Adds Cloudbees folder plugin support

This patch adds cloudbees folder plugin support. Folder should be
specified in job name : <folder>/<job>

This update also extend job_builder
(http://ci.openstack.org/jenkins-job-builder/) tool without any
modification.

Change-Id: I6d3957c217e1253e53152d90d5fcce5e69c77674
Co-Authored-By: Darragh Bailey <daragh.bailey@gmail.com>
This commit is contained in:
Jerome Hourquebie 2015-05-05 16:56:09 +02:00 committed by Darragh Bailey
parent f80e54b4bc
commit 7267eec454
22 changed files with 847 additions and 38 deletions

View File

@ -23,6 +23,7 @@ the things you can use it for:
* Create/delete/reconfig views
* Put server in shutdown mode (quiet down)
* List running builds
* Create/delete/update folders [#f1]_
* and many more..
To install::
@ -76,3 +77,11 @@ Then install the required python packages using pip_::
.. _flake8: https://pypi.python.org/pypi/flake8
.. _tox: https://testrun.org/tox
.. _pip: https://pypi.python.org/pypi/pip
.. rubric:: Footnotes
.. [#f1] The free `Cloudbees Folders Plugin
<https://wiki.jenkins-ci.org/display/JENKINS/CloudBees+Folders+Plugin>`_
provides support for a subset of the full folders functionality. For the
complete capabilities you will need the paid for version of the plugin.

View File

@ -116,3 +116,21 @@ This is an example showing how to retrieve information on the Jenkins queue.
queue_info = server.get_queue_info()
id = queue_info[0].get('id')
server.cancel_queue(id)
Example 7: Working with Jenkins Cloudbees Folders
-------------------------------------------------
Requires the `Cloudbees Folders Plugin
<https://wiki.jenkins-ci.org/display/JENKINS/CloudBees+Folders+Plugin>`_ for
Jenkins.
This is an example showing how to create, configure and delete Jenkins folders.
::
server.create_job('folder', jenkins.EMPTY_FOLDER_XML)
server.create_job('folder/empty', jenkins.EMPTY_FOLDER_XML)
server.copy_job('folder/empty', 'folder/empty_copy')
server.delete_job('folder/empty_copy')
server.delete_job('folder')

View File

@ -74,22 +74,23 @@ DEFAULT_HEADERS = {'Content-Type': 'text/xml; charset=utf-8'}
INFO = 'api/json'
PLUGIN_INFO = 'pluginManager/api/json?depth=%(depth)s'
CRUMB_URL = 'crumbIssuer/api/json'
JOB_INFO = 'job/%(name)s/api/json?depth=%(depth)s'
JOB_NAME = 'job/%(name)s/api/json?tree=name'
JOBS_QUERY = '?tree=jobs[url,color,name,jobs]'
JOB_INFO = '%(folder_url)sjob/%(short_name)s/api/json?depth=%(depth)s'
JOB_NAME = '%(folder_url)sjob/%(short_name)s/api/json?tree=name'
Q_INFO = 'queue/api/json?depth=0'
CANCEL_QUEUE = 'queue/cancelItem?id=%(id)s'
CREATE_JOB = 'createItem?name=%(name)s' # also post config.xml
CONFIG_JOB = 'job/%(name)s/config.xml'
DELETE_JOB = 'job/%(name)s/doDelete'
ENABLE_JOB = 'job/%(name)s/enable'
DISABLE_JOB = 'job/%(name)s/disable'
COPY_JOB = 'createItem?name=%(to_name)s&mode=copy&from=%(from_name)s'
RENAME_JOB = 'job/%(from_name)s/doRename?newName=%(to_name)s'
BUILD_JOB = 'job/%(name)s/build'
STOP_BUILD = 'job/%(name)s/%(number)s/stop'
BUILD_WITH_PARAMS_JOB = 'job/%(name)s/buildWithParameters'
BUILD_INFO = 'job/%(name)s/%(number)d/api/json?depth=%(depth)s'
BUILD_CONSOLE_OUTPUT = 'job/%(name)s/%(number)d/consoleText'
CREATE_JOB = '%(folder_url)screateItem?name=%(short_name)s' # also post config.xml
CONFIG_JOB = '%(folder_url)sjob/%(short_name)s/config.xml'
DELETE_JOB = '%(folder_url)sjob/%(short_name)s/doDelete'
ENABLE_JOB = '%(folder_url)sjob/%(short_name)s/enable'
DISABLE_JOB = '%(folder_url)sjob/%(short_name)s/disable'
COPY_JOB = '%(from_folder_url)screateItem?name=%(to_short_name)s&mode=copy&from=%(from_short_name)s'
RENAME_JOB = '%(from_folder_url)sjob/%(from_short_name)s/doRename?newName=%(to_short_name)s'
BUILD_JOB = '%(folder_url)sjob/%(short_name)s/build'
STOP_BUILD = '%(folder_url)sjob/%(short_name)s/%(number)s/stop'
BUILD_WITH_PARAMS_JOB = '%(folder_url)sjob/%(short_name)s/buildWithParameters'
BUILD_INFO = '%(folder_url)sjob/%(short_name)s/%(number)d/api/json?depth=%(depth)s'
BUILD_CONSOLE_OUTPUT = '%(folder_url)sjob/%(short_name)s/%(number)d/consoleText'
NODE_LIST = 'computer/api/json'
CREATE_NODE = 'computer/doCreateItem?%s'
DELETE_NODE = 'computer/%(name)s/doDelete'
@ -224,7 +225,8 @@ class Jenkins(object):
def _get_encoded_params(self, params):
for k, v in params.items():
if k in ["name", "to_name", "from_name", "msg"]:
if k in ["name", "msg", "short_name", "from_short_name",
"to_short_name", "folder_url", "from_folder_url", "to_folder_url"]:
params[k] = quote(v)
return params
@ -257,6 +259,7 @@ class Jenkins(object):
:param depth: JSON depth, ``int``
:returns: dictionary of job information
'''
folder_url, short_name = self._get_job_folder(name)
try:
response = self.jenkins_open(Request(
self._build_url(JOB_INFO, locals())
@ -271,16 +274,17 @@ class Jenkins(object):
raise JenkinsException(
"Could not parse JSON info for job[%s]" % name)
def get_job_info_regex(self, pattern, depth=0):
def get_job_info_regex(self, pattern, depth=0, folder_depth=0):
'''Get a list of jobs information that contain names which match the
regex pattern.
:param pattern: regex pattern, ``str``
:param depth: JSON depth, ``int``
:param folder_depth: folder level depth to search ``int``
:returns: List of jobs info, ``list``
'''
result = []
jobs = self.get_jobs()
jobs = self.get_all_jobs(folder_depth)
for job in jobs:
if re.search(pattern, job['name']):
result.append(self.get_job_info(job['name'], depth=depth))
@ -297,6 +301,7 @@ class Jenkins(object):
:param name: Job name, ``str``
:returns: Name of job or None
'''
folder_url, short_name = self._get_job_folder(name)
try:
response = self.jenkins_open(Request(
self._build_url(JOB_NAME, locals())
@ -305,7 +310,7 @@ class Jenkins(object):
return None
else:
actual = json.loads(response)['name']
if actual != name:
if actual != short_name:
raise JenkinsException(
'Jenkins returned an unexpected job name %s '
'(expected: %s)' % (actual, name))
@ -375,6 +380,7 @@ class Jenkins(object):
>>> print(build_info)
{u'building': False, u'changeSet': {u'items': [{u'date': u'2011-12-19T18:01:52.540557Z', u'msg': u'test', u'revision': 66, u'user': u'unknown', u'paths': [{u'editType': u'edit', u'file': u'/branches/demo/index.html'}]}], u'kind': u'svn', u'revisions': [{u'module': u'http://eaas-svn01.i3.level3.com/eaas', u'revision': 66}]}, u'builtOn': u'', u'description': None, u'artifacts': [{u'relativePath': u'dist/eaas-87-2011-12-19_18-01-57.war', u'displayPath': u'eaas-87-2011-12-19_18-01-57.war', u'fileName': u'eaas-87-2011-12-19_18-01-57.war'}, {u'relativePath': u'dist/eaas-87-2011-12-19_18-01-57.war.zip', u'displayPath': u'eaas-87-2011-12-19_18-01-57.war.zip', u'fileName': u'eaas-87-2011-12-19_18-01-57.war.zip'}], u'timestamp': 1324317717000, u'number': 87, u'actions': [{u'parameters': [{u'name': u'SERVICE_NAME', u'value': u'eaas'}, {u'name': u'PROJECT_NAME', u'value': u'demo'}]}, {u'causes': [{u'userName': u'anonymous', u'shortDescription': u'Started by user anonymous'}]}, {}, {}, {}], u'id': u'2011-12-19_18-01-57', u'keepLog': False, u'url': u'http://eaas-jenkins01.i3.level3.com:9080/job/build_war/87/', u'culprits': [{u'absoluteUrl': u'http://eaas-jenkins01.i3.level3.com:9080/user/unknown', u'fullName': u'unknown'}], u'result': u'SUCCESS', u'duration': 8826, u'fullDisplayName': u'build_war #87'}
'''
folder_url, short_name = self._get_job_folder(name)
try:
response = self.jenkins_open(Request(
self._build_url(BUILD_INFO, locals())
@ -421,12 +427,15 @@ class Jenkins(object):
# mechanism, so ignore it
pass
def get_info(self):
"""Get information on this Master.
def get_info(self, item="", query=None):
"""Get information on this Master or item on Master.
This information includes job list and view information.
This information includes job list and view information and can be
used to retreive information on items such as job folders.
:returns: dictionary of information about Master, ``dict``
:param item: item to get information about on this Master
:param query: xpath to extract information about on this Master
:returns: dictionary of information about Master or item, ``dict``
Example::
@ -437,9 +446,12 @@ class Jenkins(object):
u'name': u'my_job'}
"""
url = "/".join((item, INFO))
if query:
url += query
try:
return json.loads(self.jenkins_open(
Request(self._build_url(INFO))
Request(self._build_url(url))
))
except (HTTPError, BadStatusLine):
raise BadHTTPException("Error communicating with server[%s]"
@ -549,21 +561,106 @@ class Jenkins(object):
raise JenkinsException("Could not parse JSON info for server[%s]"
% self.server)
def get_jobs(self):
"""Get list of jobs running.
def get_jobs(self, folder_depth=0):
"""Get list of jobs.
Each job is a dictionary with 'name', 'url', and 'color' keys.
Each job is a dictionary with 'name', 'url', 'color' and 'fullname'
keys.
:param folder_depth: Number of levels to search, ``int``. By default
0, which will limit search to toplevel. None disables the limit.
:returns: list of jobs, ``[ { str: str} ]``
"""
return self.get_info()['jobs']
return self.get_all_jobs(folder_depth=folder_depth)
def get_all_jobs(self, folder_depth=None):
"""Get list of all jobs recursively to the given folder depth.
Each job is a dictionary with 'name', 'url', 'color' and 'fullname'
keys.
:param folder_depth: Number of levels to search, ``int``. By default
None, which will search all levels. 0 limits to toplevel.
:returns: list of jobs, ``[ { str: str} ]``
.. note::
On instances with many folders it may be more efficient to use the
run_script method to retrieve all jobs instead.
Example::
server.run_script(\"\"\"
import groovy.json.JsonBuilder;
// get all projects excluding matrix configuration
// as they are simply part of a matrix project.
// there may be better ways to get just jobs
items = Jenkins.instance.getAllItems(AbstractProject);
items.removeAll {
it instanceof hudson.matrix.MatrixConfiguration
};
def json = new JsonBuilder()
def root = json {
jobs items.collect {
[
name: it.name,
url: Jenkins.instance.getRootUrl() + it.getUrl(),
color: it.getIconColor().toString(),
fullname: it.getFullName()
]
}
}
// use json.toPrettyString() if viewing
println json.toString()
\"\"\")
"""
jobs_list = []
jobs = [(0, "", self.get_info(query=JOBS_QUERY)['jobs'])]
for lvl, root, lvl_jobs in jobs:
if not isinstance(lvl_jobs, list):
lvl_jobs = [lvl_jobs]
for job in lvl_jobs:
if 'jobs' in job: # folder
if folder_depth is None or lvl < folder_depth:
path = '/job/'.join((root, job[u'name']))
jobs.append(
(lvl + 1, path,
self.get_info(path,
query=JOBS_QUERY)['jobs']))
else:
# insert fullname info if it doesn't exist to
# allow callers to easily reference unambiguously
if u'fullname' not in job:
job[u'fullname'] = '/'.join(
[p for p in root.split('/')
if p and p != 'job'] +
[job[u'name']])
jobs_list.append(job)
return jobs_list
def copy_job(self, from_name, to_name):
'''Copy a Jenkins job
'''Copy a Jenkins job.
Will raise an exception whenever the source and destination folder
for this jobs won't be the same.
:param from_name: Name of Jenkins job to copy from, ``str``
:param to_name: Name of Jenkins job to copy to, ``str``
:throws: :class:`JenkinsException` whenever the source and destination
folder are not the same
'''
from_folder_url, from_short_name = self._get_job_folder(from_name)
to_folder_url, to_short_name = self._get_job_folder(to_name)
if from_folder_url != to_folder_url:
raise JenkinsException('copy[%s to %s] failed, source and destination '
'folder must be the same' % (from_name, to_name))
self.jenkins_open(Request(
self._build_url(COPY_JOB, locals()), b''))
self.assert_job_exists(to_name, 'create[%s] failed')
@ -571,9 +668,19 @@ class Jenkins(object):
def rename_job(self, from_name, to_name):
'''Rename an existing Jenkins job
Will raise an exception whenever the source and destination folder
for this jobs won't be the same.
:param from_name: Name of Jenkins job to rename, ``str``
:param to_name: New Jenkins job name, ``str``
:throws: :class:`JenkinsException` whenever the source and destination
folder are not the same
'''
from_folder_url, from_short_name = self._get_job_folder(from_name)
to_folder_url, to_short_name = self._get_job_folder(to_name)
if from_folder_url != to_folder_url:
raise JenkinsException('rename[%s to %s] failed, source and destination folder '
'must be the same' % (from_name, to_name))
self.jenkins_open(Request(
self._build_url(RENAME_JOB, locals()), b''))
self.assert_job_exists(to_name, 'rename[%s] failed')
@ -583,6 +690,7 @@ class Jenkins(object):
:param name: Name of Jenkins job, ``str``
'''
folder_url, short_name = self._get_job_folder(name)
self.jenkins_open(Request(
self._build_url(DELETE_JOB, locals()), b''))
if self.job_exists(name):
@ -593,6 +701,7 @@ class Jenkins(object):
:param name: Name of Jenkins job, ``str``
'''
folder_url, short_name = self._get_job_folder(name)
self.jenkins_open(Request(
self._build_url(ENABLE_JOB, locals()), b''))
@ -603,6 +712,7 @@ class Jenkins(object):
:param name: Name of Jenkins job, ``str``
'''
folder_url, short_name = self._get_job_folder(name)
self.jenkins_open(Request(
self._build_url(DISABLE_JOB, locals()), b''))
@ -612,15 +722,32 @@ class Jenkins(object):
:param name: Name of Jenkins job, ``str``
:returns: ``True`` if Jenkins job exists
'''
if self.get_job_name(name) == name:
folder_url, short_name = self._get_job_folder(name)
if self.get_job_name(name) == short_name:
return True
def jobs_count(self):
'''Get the number of jobs on the Jenkins server
:returns: Total number of jobs, ``int``
.. note::
On instances with many folders it may be more efficient to use the
run_script method to retrieve the total number of jobs instead.
Example::
# get all projects excluding matrix configuration
# as they are simply part of a matrix project.
server.run_script(
"print(Hudson.instance.getAllItems("
" hudson.model.AbstractProject).count{"
" !(it instanceof hudson.matrix.MatrixConfiguration)"
" })")
'''
return len(self.get_jobs())
return len(self.get_all_jobs())
def assert_job_exists(self, name,
exception_message='job[%s] does not exist'):
@ -640,12 +767,17 @@ class Jenkins(object):
:param name: Name of Jenkins job, ``str``
:param config_xml: config file text, ``str``
'''
folder_url, short_name = self._get_job_folder(name)
if self.job_exists(name):
raise JenkinsException('job[%s] already exists' % (name))
self.jenkins_open(Request(
self._build_url(CREATE_JOB, locals()),
config_xml.encode('utf-8'), DEFAULT_HEADERS))
try:
self.jenkins_open(Request(
self._build_url(CREATE_JOB, locals()),
config_xml.encode('utf-8'), DEFAULT_HEADERS))
except NotFoundException:
raise JenkinsException('Cannot create job[%s] because folder '
'for the job does not exist' % (name))
self.assert_job_exists(name, 'create[%s] failed')
def get_job_config(self, name):
@ -654,6 +786,7 @@ class Jenkins(object):
:param name: Name of Jenkins job, ``str``
:returns: job configuration (XML format)
'''
folder_url, short_name = self._get_job_folder(name)
request = Request(self._build_url(CONFIG_JOB, locals()))
return self.jenkins_open(request)
@ -665,6 +798,7 @@ class Jenkins(object):
:param name: Name of Jenkins job, ``str``
:param config_xml: New XML configuration, ``str``
'''
folder_url, short_name = self._get_job_folder(name)
reconfig_url = self._build_url(CONFIG_JOB, locals())
self.jenkins_open(Request(reconfig_url, config_xml.encode('utf-8'),
DEFAULT_HEADERS))
@ -679,6 +813,7 @@ class Jenkins(object):
:param token: (optional) token for building job, ``str``
:returns: URL for building job
'''
folder_url, short_name = self._get_job_folder(name)
if parameters:
if token:
parameters['token'] = token
@ -723,6 +858,7 @@ class Jenkins(object):
:param name: Name of Jenkins job, ``str``
:param number: Jenkins build number for the job, ``int``
'''
folder_url, short_name = self._get_job_folder(name)
self.jenkins_open(Request(
self._build_url(STOP_BUILD, locals()), b''))
@ -937,6 +1073,7 @@ class Jenkins(object):
:param name: Build number, ``int``
:returns: Build console output, ``str``
'''
folder_url, short_name = self._get_job_folder(name)
try:
response = self.jenkins_open(Request(
self._build_url(BUILD_CONSOLE_OUTPUT, locals())
@ -950,6 +1087,24 @@ class Jenkins(object):
raise JenkinsException('job[%s] number[%d] does not exist'
% (name, number))
def _get_job_folder(self, name):
'''Return the name and folder (see cloudbees plugin).
This is a method to support cloudbees folder plugin.
Url request should take into account folder path when the job name specify it
(ex.: 'folder/job')
:param name: Job name, ``str``
:returns: Tuple [ 'folder path for Request', 'Name of job without folder path' ]
'''
a_path = name.split('/')
short_name = a_path[-1]
folder_url = (('job/' + '/job/'.join(a_path[:-1]) + '/')
if len(a_path) > 1 else '')
return folder_url, short_name
def get_view_name(self, name):
'''Return the name of a view using the API.

View File

@ -4,6 +4,6 @@ hacking>=0.5.6,<0.11
mock<1.1
unittest2
python-subunit
sphinx>=1.1.2,<1.2
sphinx>=1.2,<1.3.0
testrepository
testtools

View File

@ -1,3 +1,6 @@
import copy
import json
from tests.base import JenkinsTestBase
@ -8,3 +11,49 @@ class JenkinsJobsTestBase(JenkinsTestBase):
<actions/>
<description>Foo</description>
</matrix-project>"""
class JenkinsGetJobsTestBase(JenkinsJobsTestBase):
jobs_in_folder = [
[
{'name': 'my_job1'},
{'name': 'my_folder1', 'jobs': None},
{'name': 'my_job2'}
],
# my_folder1 jobs
[
{'name': 'my_job3'},
{'name': 'my_job4'}
]
]
jobs_in_multiple_folders = copy.deepcopy(jobs_in_folder)
jobs_in_multiple_folders[1].insert(
0, {'name': 'my_folder2', 'jobs': None})
jobs_in_multiple_folders.append(
# my_folder1/my_folder2 jobs
[
{'name': 'my_job1'},
{'name': 'my_job2'}
]
)
def build_jobs_list_responses(jobs_list, server_url):
responses = []
for jobs in jobs_list:
get_jobs_response = []
for job in jobs:
job_json = {
u'url': u'%s/job/%s' % (server_url.rstrip('/'), job['name']),
u'name': job['name'],
u'color': u'blue'
}
if 'jobs' in job:
job_json[u'jobs'] = "null"
get_jobs_response.append(job_json)
responses.append(json.dumps({u'jobs': get_jobs_response}))
return responses

View File

@ -18,6 +18,17 @@ class JenkinsAssertJobExistsTest(JenkinsJobsTestBase):
'job[NonExistent] does not exist')
self._check_requests(jenkins_mock.call_args_list)
@patch.object(jenkins.Jenkins, 'jenkins_open')
def test_job_missing_in_folder(self, jenkins_mock):
jenkins_mock.side_effect = jenkins.NotFoundException()
with self.assertRaises(jenkins.JenkinsException) as context_manager:
self.j.assert_job_exists('a Folder/NonExistent')
self.assertEqual(
str(context_manager.exception),
'job[a Folder/NonExistent] does not exist')
self._check_requests(jenkins_mock.call_args_list)
@patch.object(jenkins.Jenkins, 'jenkins_open')
def test_job_exists(self, jenkins_mock):
jenkins_mock.side_effect = [
@ -25,3 +36,11 @@ class JenkinsAssertJobExistsTest(JenkinsJobsTestBase):
]
self.j.assert_job_exists('ExistingJob')
self._check_requests(jenkins_mock.call_args_list)
@patch.object(jenkins.Jenkins, 'jenkins_open')
def test_job_exists_in_folder(self, jenkins_mock):
jenkins_mock.side_effect = [
json.dumps({'name': 'ExistingJob'}),
]
self.j.assert_job_exists('a Folder/ExistingJob')
self._check_requests(jenkins_mock.call_args_list)

View File

@ -19,6 +19,19 @@ class JenkinsBuildJobTest(JenkinsJobsTestBase):
self.assertEqual(build_info, {'foo': 'bar'})
self._check_requests(jenkins_mock.call_args_list)
@patch.object(jenkins.Jenkins, 'jenkins_open')
def test_in_folder(self, jenkins_mock):
jenkins_mock.side_effect = [
{'foo': 'bar'},
]
build_info = self.j.build_job(u'a Folder/Test Job')
self.assertEqual(jenkins_mock.call_args[0][0].get_full_url(),
u'http://example.com/job/a%20Folder/job/Test%20Job/build')
self.assertEqual(build_info, {'foo': 'bar'})
self._check_requests(jenkins_mock.call_args_list)
@patch.object(jenkins.Jenkins, 'jenkins_open')
def test_with_token(self, jenkins_mock):
jenkins_mock.side_effect = [
@ -32,6 +45,19 @@ class JenkinsBuildJobTest(JenkinsJobsTestBase):
self.assertEqual(build_info, {'foo': 'bar'})
self._check_requests(jenkins_mock.call_args_list)
@patch.object(jenkins.Jenkins, 'jenkins_open')
def test_in_folder_with_token(self, jenkins_mock):
jenkins_mock.side_effect = [
{'foo': 'bar'},
]
build_info = self.j.build_job(u'a Folder/TestJob', token='some_token')
self.assertEqual(jenkins_mock.call_args[0][0].get_full_url(),
u'http://example.com/job/a%20Folder/job/TestJob/build?token=some_token')
self.assertEqual(build_info, {'foo': 'bar'})
self._check_requests(jenkins_mock.call_args_list)
@patch.object(jenkins.Jenkins, 'jenkins_open')
def test_with_parameters_and_token(self, jenkins_mock):
jenkins_mock.side_effect = [

View File

@ -14,3 +14,12 @@ class JenkinsGetJobConfigTest(JenkinsJobsTestBase):
jenkins_mock.call_args[0][0].get_full_url(),
u'http://example.com/job/Test%20Job/config.xml')
self._check_requests(jenkins_mock.call_args_list)
@patch.object(jenkins.Jenkins, 'jenkins_open')
def test_encodes_job_name_in_folder(self, jenkins_mock):
self.j.get_job_config(u'a folder/Test Job')
self.assertEqual(
jenkins_mock.call_args[0][0].get_full_url(),
u'http://example.com/job/a%20folder/job/Test%20Job/config.xml')
self._check_requests(jenkins_mock.call_args_list)

View File

@ -24,6 +24,23 @@ class JenkinsCopyJobTest(JenkinsJobsTestBase):
self.assertTrue(self.j.job_exists('Test Job_2'))
self._check_requests(jenkins_mock.call_args_list)
@patch.object(jenkins.Jenkins, 'jenkins_open')
def test_in_folder(self, jenkins_mock):
jenkins_mock.side_effect = [
json.dumps({'name': 'Test Job_2'}),
json.dumps({'name': 'Test Job_2'}),
json.dumps({'name': 'Test Job_2'}),
]
self.j.copy_job(u'a Folder/Test Job', u'a Folder/Test Job_2')
self.assertEqual(
jenkins_mock.call_args_list[0][0][0].get_full_url(),
'http://example.com/job/a%20Folder/createItem'
'?name=Test%20Job_2&mode=copy&from=Test%20Job')
self.assertTrue(self.j.job_exists('a Folder/Test Job_2'))
self._check_requests(jenkins_mock.call_args_list)
@patch.object(jenkins.Jenkins, 'jenkins_open')
def test_failed(self, jenkins_mock):
jenkins_mock.side_effect = [
@ -41,3 +58,35 @@ class JenkinsCopyJobTest(JenkinsJobsTestBase):
str(context_manager.exception),
'create[TestJob_2] failed')
self._check_requests(jenkins_mock.call_args_list)
@patch.object(jenkins.Jenkins, 'jenkins_open')
def test_in_folder_failed(self, jenkins_mock):
jenkins_mock.side_effect = [
None,
jenkins.NotFoundException(),
]
with self.assertRaises(jenkins.JenkinsException) as context_manager:
self.j.copy_job(u'a Folder/TestJob', u'a Folder/TestJob_2')
self.assertEqual(
jenkins_mock.call_args_list[0][0][0].get_full_url(),
'http://example.com/job/a%20Folder/createItem'
'?name=TestJob_2&mode=copy&from=TestJob')
self.assertEqual(
str(context_manager.exception),
'create[a Folder/TestJob_2] failed')
self._check_requests(jenkins_mock.call_args_list)
@patch.object(jenkins.Jenkins, 'jenkins_open')
def test_in_another_folder_failed(self, jenkins_mock):
jenkins_mock.side_effect = [
jenkins.JenkinsException()
]
with self.assertRaises(jenkins.JenkinsException) as context_manager:
self.j.copy_job(u'a Folder/TestJob', u'another Folder/TestJob_2')
self.assertEqual(
str(context_manager.exception),
('copy[a Folder/TestJob to another Folder/TestJob_2] failed, '
'source and destination folder must be the same'))
self._check_requests(jenkins_mock.call_args_list)

View File

@ -22,6 +22,21 @@ class JenkinsCreateJobTest(JenkinsJobsTestBase):
'http://example.com/createItem?name=Test%20Job')
self._check_requests(jenkins_mock.call_args_list)
@patch.object(jenkins.Jenkins, 'jenkins_open')
def test_in_folder(self, jenkins_mock):
jenkins_mock.side_effect = [
jenkins.NotFoundException(),
None,
json.dumps({'name': 'Test Job'}),
]
self.j.create_job(u'a Folder/Test Job', self.config_xml)
self.assertEqual(
jenkins_mock.call_args_list[1][0][0].get_full_url(),
'http://example.com/job/a%20Folder/createItem?name=Test%20Job')
self._check_requests(jenkins_mock.call_args_list)
@patch.object(jenkins.Jenkins, 'jenkins_open')
def test_already_exists(self, jenkins_mock):
jenkins_mock.side_effect = [
@ -39,6 +54,23 @@ class JenkinsCreateJobTest(JenkinsJobsTestBase):
'job[TestJob] already exists')
self._check_requests(jenkins_mock.call_args_list)
@patch.object(jenkins.Jenkins, 'jenkins_open')
def test_already_exists_in_folder(self, jenkins_mock):
jenkins_mock.side_effect = [
json.dumps({'name': 'TestJob'}),
None,
]
with self.assertRaises(jenkins.JenkinsException) as context_manager:
self.j.create_job(u'a Folder/TestJob', self.config_xml)
self.assertEqual(
jenkins_mock.call_args_list[0][0][0].get_full_url(),
'http://example.com/job/a%20Folder/job/TestJob/api/json?tree=name')
self.assertEqual(
str(context_manager.exception),
'job[a Folder/TestJob] already exists')
self._check_requests(jenkins_mock.call_args_list)
@patch.object(jenkins.Jenkins, 'jenkins_open')
def test_failed(self, jenkins_mock):
jenkins_mock.side_effect = [
@ -59,3 +91,24 @@ class JenkinsCreateJobTest(JenkinsJobsTestBase):
str(context_manager.exception),
'create[TestJob] failed')
self._check_requests(jenkins_mock.call_args_list)
@patch.object(jenkins.Jenkins, 'jenkins_open')
def test_failed_in_folder(self, jenkins_mock):
jenkins_mock.side_effect = [
jenkins.NotFoundException(),
None,
jenkins.NotFoundException(),
]
with self.assertRaises(jenkins.JenkinsException) as context_manager:
self.j.create_job(u'a Folder/TestJob', self.config_xml)
self.assertEqual(
jenkins_mock.call_args_list[0][0][0].get_full_url(),
'http://example.com/job/a%20Folder/job/TestJob/api/json?tree=name')
self.assertEqual(
jenkins_mock.call_args_list[1][0][0].get_full_url(),
'http://example.com/job/a%20Folder/createItem?name=TestJob')
self.assertEqual(
str(context_manager.exception),
'create[a Folder/TestJob] failed')
self._check_requests(jenkins_mock.call_args_list)

View File

@ -23,3 +23,20 @@ class JenkinsDebugJobInfoTest(JenkinsJobsTestBase):
jenkins_mock.call_args[0][0].get_full_url(),
u'http://example.com/job/Test%20Job/api/json?depth=0')
self._check_requests(jenkins_mock.call_args_list)
@patch.object(jenkins.Jenkins, 'jenkins_open')
def test_in_folder(self, jenkins_mock):
job_info_to_return = {
u'building': False,
u'msg': u'test',
u'revision': 66,
u'user': u'unknown'
}
jenkins_mock.return_value = json.dumps(job_info_to_return)
self.j.debug_job_info(u'a Folder/Test Job')
self.assertEqual(
jenkins_mock.call_args[0][0].get_full_url(),
u'http://example.com/job/a%20Folder/job/Test%20Job/api/json?depth=0')
self._check_requests(jenkins_mock.call_args_list)

View File

@ -21,6 +21,20 @@ class JenkinsDeleteJobTest(JenkinsJobsTestBase):
'http://example.com/job/Test%20Job/doDelete')
self._check_requests(jenkins_mock.call_args_list)
@patch.object(jenkins.Jenkins, 'jenkins_open')
def test_in_folder(self, jenkins_mock):
jenkins_mock.side_effect = [
None,
jenkins.NotFoundException(),
]
self.j.delete_job(u'a Folder/Test Job')
self.assertEqual(
jenkins_mock.call_args_list[0][0][0].get_full_url(),
'http://example.com/job/a%20Folder/job/Test%20Job/doDelete')
self._check_requests(jenkins_mock.call_args_list)
@patch.object(jenkins.Jenkins, 'jenkins_open')
def test_failed(self, jenkins_mock):
jenkins_mock.side_effect = [
@ -38,3 +52,21 @@ class JenkinsDeleteJobTest(JenkinsJobsTestBase):
str(context_manager.exception),
'delete[TestJob] failed')
self._check_requests(jenkins_mock.call_args_list)
@patch.object(jenkins.Jenkins, 'jenkins_open')
def test_in_folder_failed(self, jenkins_mock):
jenkins_mock.side_effect = [
json.dumps({'name': 'TestJob'}),
json.dumps({'name': 'TestJob'}),
json.dumps({'name': 'TestJob'}),
]
with self.assertRaises(jenkins.JenkinsException) as context_manager:
self.j.delete_job(u'a Folder/TestJob')
self.assertEqual(
jenkins_mock.call_args_list[0][0][0].get_full_url(),
'http://example.com/job/a%20Folder/job/TestJob/doDelete')
self.assertEqual(
str(context_manager.exception),
'delete[a Folder/TestJob] failed')
self._check_requests(jenkins_mock.call_args_list)

View File

@ -21,3 +21,18 @@ class JenkinsDisableJobTest(JenkinsJobsTestBase):
'http://example.com/job/Test%20Job/disable')
self.assertTrue(self.j.job_exists('Test Job'))
self._check_requests(jenkins_mock.call_args_list)
@patch.object(jenkins.Jenkins, 'jenkins_open')
def test_in_folder(self, jenkins_mock):
jenkins_mock.side_effect = [
json.dumps({'name': 'Test Job'}),
json.dumps({'name': 'Test Job'}),
]
self.j.disable_job(u'a Folder/Test Job')
self.assertEqual(
jenkins_mock.call_args_list[0][0][0].get_full_url(),
'http://example.com/job/a%20Folder/job/Test%20Job/disable')
self.assertTrue(self.j.job_exists('a Folder/Test Job'))
self._check_requests(jenkins_mock.call_args_list)

View File

@ -21,3 +21,18 @@ class JenkinsEnableJobTest(JenkinsJobsTestBase):
'http://example.com/job/TestJob/enable')
self.assertTrue(self.j.job_exists('TestJob'))
self._check_requests(jenkins_mock.call_args_list)
@patch.object(jenkins.Jenkins, 'jenkins_open')
def test_in_folder(self, jenkins_mock):
jenkins_mock.side_effect = [
json.dumps({'name': 'TestJob'}),
json.dumps({'name': 'TestJob'}),
]
self.j.enable_job(u'a Folder/TestJob')
self.assertEqual(
jenkins_mock.call_args_list[0][0][0].get_full_url(),
'http://example.com/job/a%20Folder/job/TestJob/enable')
self.assertTrue(self.j.job_exists('a Folder/TestJob'))
self._check_requests(jenkins_mock.call_args_list)

View File

@ -2,10 +2,11 @@ import json
from mock import patch
import jenkins
from tests.jobs.base import JenkinsJobsTestBase
from tests.jobs.base import build_jobs_list_responses
from tests.jobs.base import JenkinsGetJobsTestBase
class JenkinsGetJobsTest(JenkinsJobsTestBase):
class JenkinsGetJobsTest(JenkinsGetJobsTestBase):
@patch.object(jenkins.Jenkins, 'jenkins_open')
def test_simple(self, jenkins_mock):
@ -19,8 +20,40 @@ class JenkinsGetJobsTest(JenkinsJobsTestBase):
job_info = self.j.get_jobs()
self.assertEqual(job_info, jobs)
jobs[u'fullname'] = jobs[u'name']
self.assertEqual(job_info, [jobs])
self.assertEqual(
jenkins_mock.call_args[0][0].get_full_url(),
u'http://example.com/api/json')
u'http://example.com/api/json?tree=jobs[url,color,name,jobs]')
self._check_requests(jenkins_mock.call_args_list)
@patch.object(jenkins.Jenkins, 'jenkins_open')
def test_folders_simple(self, jenkins_mock):
response = build_jobs_list_responses(
self.jobs_in_folder, 'http://example.com/')
jenkins_mock.side_effect = iter(response)
jobs_info = self.j.get_jobs()
expected_fullnames = [
u"my_job1", u"my_job2"
]
self.assertEqual(len(expected_fullnames), len(jobs_info))
got_fullnames = [job[u"fullname"] for job in jobs_info]
self.assertEqual(expected_fullnames, got_fullnames)
@patch.object(jenkins.Jenkins, 'jenkins_open')
def test_folders_additional_level(self, jenkins_mock):
response = build_jobs_list_responses(
self.jobs_in_folder, 'http://example.com/')
jenkins_mock.side_effect = iter(response)
jobs_info = self.j.get_jobs(folder_depth=1)
expected_fullnames = [
u"my_job1", u"my_job2",
u"my_folder1/my_job3", u"my_folder1/my_job4"
]
self.assertEqual(len(expected_fullnames), len(jobs_info))
got_fullnames = [job[u"fullname"] for job in jobs_info]
self.assertEqual(expected_fullnames, got_fullnames)

61
tests/jobs/test_getall.py Normal file
View File

@ -0,0 +1,61 @@
from mock import patch
import jenkins
from tests.jobs.base import build_jobs_list_responses
from tests.jobs.base import JenkinsGetJobsTestBase
class JenkinsGetAllJobsTest(JenkinsGetJobsTestBase):
@patch.object(jenkins.Jenkins, 'jenkins_open')
def test_simple(self, jenkins_mock):
response = build_jobs_list_responses(
self.jobs_in_folder, 'http://example.com/')
jenkins_mock.side_effect = iter(response)
jobs_info = self.j.get_all_jobs()
expected_fullnames = [
u"my_job1", u"my_job2",
u"my_folder1/my_job3", u"my_folder1/my_job4"
]
self.assertEqual(len(expected_fullnames), len(jobs_info))
got_fullnames = [job[u"fullname"] for job in jobs_info]
self.assertEqual(expected_fullnames, got_fullnames)
@patch.object(jenkins.Jenkins, 'jenkins_open')
def test_multi_level(self, jenkins_mock):
response = build_jobs_list_responses(
self.jobs_in_multiple_folders, 'http://example.com/')
jenkins_mock.side_effect = iter(response)
jobs_info = self.j.get_all_jobs()
expected_fullnames = [
u"my_job1", u"my_job2",
u"my_folder1/my_job3", u"my_folder1/my_job4",
u"my_folder1/my_folder2/my_job1", u"my_folder1/my_folder2/my_job2"
]
self.assertEqual(len(expected_fullnames), len(jobs_info))
got_fullnames = [job[u"fullname"] for job in jobs_info]
self.assertEqual(expected_fullnames, got_fullnames)
# multiple jobs with same name
self.assertEqual(2, len([True
for job in jobs_info
if job['name'] == u"my_job1"]))
@patch.object(jenkins.Jenkins, 'jenkins_open')
def test_folders_depth(self, jenkins_mock):
response = build_jobs_list_responses(
self.jobs_in_multiple_folders, 'http://example.com/')
jenkins_mock.side_effect = iter(response)
jobs_info = self.j.get_all_jobs(folder_depth=1)
expected_fullnames = [
u"my_job1", u"my_job2",
u"my_folder1/my_job3", u"my_folder1/my_job4"
]
self.assertEqual(len(expected_fullnames), len(jobs_info))
got_fullnames = [job[u"fullname"] for job in jobs_info]
self.assertEqual(expected_fullnames, got_fullnames)

View File

@ -25,6 +25,24 @@ class JenkinsGetJobInfoTest(JenkinsJobsTestBase):
u'http://example.com/job/Test%20Job/api/json?depth=0')
self._check_requests(jenkins_mock.call_args_list)
@patch.object(jenkins.Jenkins, 'jenkins_open')
def test_in_folder(self, jenkins_mock):
job_info_to_return = {
u'building': False,
u'msg': u'test',
u'revision': 66,
u'user': u'unknown'
}
jenkins_mock.return_value = json.dumps(job_info_to_return)
job_info = self.j.get_job_info(u'a Folder/Test Job')
self.assertEqual(job_info, job_info_to_return)
self.assertEqual(
jenkins_mock.call_args[0][0].get_full_url(),
u'http://example.com/job/a%20Folder/job/Test%20Job/api/json?depth=0')
self._check_requests(jenkins_mock.call_args_list)
@patch.object(jenkins.Jenkins, 'jenkins_open')
def test_regex(self, jenkins_mock):
jobs = [
@ -91,3 +109,22 @@ class JenkinsGetJobInfoTest(JenkinsJobsTestBase):
str(context_manager.exception),
'job[TestJob] does not exist')
self._check_requests(jenkins_mock.call_args_list)
@patch.object(jenkins.Jenkins, 'jenkins_open')
def test_in_folder_raise_HTTPError(self, jenkins_mock):
jenkins_mock.side_effect = jenkins.HTTPError(
'http://example.com/job/a%20Folder/job/TestJob/api/json?depth=0',
code=401,
msg="basic auth failed",
hdrs=[],
fp=None)
with self.assertRaises(jenkins.JenkinsException) as context_manager:
self.j.get_job_info(u'a Folder/TestJob')
self.assertEqual(
jenkins_mock.call_args[0][0].get_full_url(),
u'http://example.com/job/a%20Folder/job/TestJob/api/json?depth=0')
self.assertEqual(
str(context_manager.exception),
'job[a Folder/TestJob] does not exist')
self._check_requests(jenkins_mock.call_args_list)

View File

@ -20,6 +20,19 @@ class JenkinsGetJobNameTest(JenkinsJobsTestBase):
u'http://example.com/job/Test%20Job/api/json?tree=name')
self._check_requests(jenkins_mock.call_args_list)
@patch.object(jenkins.Jenkins, 'jenkins_open')
def test_in_folder(self, jenkins_mock):
job_name_to_return = {u'name': 'Test Job'}
jenkins_mock.return_value = json.dumps(job_name_to_return)
job_name = self.j.get_job_name(u'a Folder/Test Job')
self.assertEqual(job_name, 'Test Job')
self.assertEqual(
jenkins_mock.call_args[0][0].get_full_url(),
u'http://example.com/job/a%20Folder/job/Test%20Job/api/json?tree=name')
self._check_requests(jenkins_mock.call_args_list)
@patch.object(jenkins.Jenkins, 'jenkins_open')
def test_return_none(self, jenkins_mock):
jenkins_mock.side_effect = jenkins.NotFoundException()
@ -32,6 +45,18 @@ class JenkinsGetJobNameTest(JenkinsJobsTestBase):
u'http://example.com/job/TestJob/api/json?tree=name')
self._check_requests(jenkins_mock.call_args_list)
@patch.object(jenkins.Jenkins, 'jenkins_open')
def test_in_folder_return_none(self, jenkins_mock):
jenkins_mock.side_effect = jenkins.NotFoundException()
job_name = self.j.get_job_name(u'a Folder/TestJob')
self.assertEqual(job_name, None)
self.assertEqual(
jenkins_mock.call_args[0][0].get_full_url(),
u'http://example.com/job/a%20Folder/job/TestJob/api/json?tree=name')
self._check_requests(jenkins_mock.call_args_list)
@patch.object(jenkins.Jenkins, 'jenkins_open')
def test_unexpected_job_name(self, jenkins_mock):
job_name_to_return = {u'name': 'not the right name'}
@ -47,3 +72,19 @@ class JenkinsGetJobNameTest(JenkinsJobsTestBase):
'Jenkins returned an unexpected job name {0} '
'(expected: {1})'.format(job_name_to_return['name'], 'TestJob'))
self._check_requests(jenkins_mock.call_args_list)
@patch.object(jenkins.Jenkins, 'jenkins_open')
def test_in_folder_unexpected_job_name(self, jenkins_mock):
job_name_to_return = {u'name': 'not the right name'}
jenkins_mock.return_value = json.dumps(job_name_to_return)
with self.assertRaises(jenkins.JenkinsException) as context_manager:
self.j.get_job_name(u'a Folder/TestJob')
self.assertEqual(
jenkins_mock.call_args_list[0][0][0].get_full_url(),
'http://example.com/job/a%20Folder/job/TestJob/api/json?tree=name')
self.assertEqual(
str(context_manager.exception),
'Jenkins returned an unexpected job name {0} (expected: '
'{1})'.format(job_name_to_return['name'], 'a Folder/TestJob'))
self._check_requests(jenkins_mock.call_args_list)

View File

@ -19,3 +19,16 @@ class JenkinsReconfigJobTest(JenkinsJobsTestBase):
self.assertEqual(jenkins_mock.call_args[0][0].get_full_url(),
u'http://example.com/job/Test%20Job/config.xml')
self._check_requests(jenkins_mock.call_args_list)
@patch.object(jenkins.Jenkins, 'jenkins_open')
def test_in_folder(self, jenkins_mock):
jenkins_mock.side_effect = [
json.dumps({'name': 'Test Job'}),
None,
]
self.j.reconfig_job(u'a Folder/Test Job', self.config_xml)
self.assertEqual(jenkins_mock.call_args[0][0].get_full_url(),
u'http://example.com/job/a%20Folder/job/Test%20Job/config.xml')
self._check_requests(jenkins_mock.call_args_list)

View File

@ -23,6 +23,22 @@ class JenkinsRenameJobTest(JenkinsJobsTestBase):
self.assertTrue(self.j.job_exists('Test Job_2'))
self._check_requests(jenkins_mock.call_args_list)
@patch.object(jenkins.Jenkins, 'jenkins_open')
def test_in_folder(self, jenkins_mock):
jenkins_mock.side_effect = [
json.dumps({'name': 'Test Job_2'}),
json.dumps({'name': 'Test Job_2'}),
json.dumps({'name': 'Test Job_2'}),
]
self.j.rename_job(u'a Folder/Test Job', u'a Folder/Test Job_2')
self.assertEqual(
jenkins_mock.call_args_list[0][0][0].get_full_url(),
'http://example.com/job/a%20Folder/job/Test%20Job/doRename?newName=Test%20Job_2')
self.assertTrue(self.j.job_exists('Test Job_2'))
self._check_requests(jenkins_mock.call_args_list)
@patch.object(jenkins.Jenkins, 'jenkins_open')
def test_failed(self, jenkins_mock):
jenkins_mock.side_effect = [
@ -39,3 +55,34 @@ class JenkinsRenameJobTest(JenkinsJobsTestBase):
str(context_manager.exception),
'rename[TestJob_2] failed')
self._check_requests(jenkins_mock.call_args_list)
@patch.object(jenkins.Jenkins, 'jenkins_open')
def test_in_folder_failed(self, jenkins_mock):
jenkins_mock.side_effect = [
None,
jenkins.NotFoundException(),
]
with self.assertRaises(jenkins.JenkinsException) as context_manager:
self.j.rename_job(u'a Folder/TestJob', u'a Folder/TestJob_2')
self.assertEqual(
jenkins_mock.call_args_list[0][0][0].get_full_url(),
'http://example.com/job/a%20Folder/job/TestJob/doRename?newName=TestJob_2')
self.assertEqual(
str(context_manager.exception),
'rename[a Folder/TestJob_2] failed')
self._check_requests(jenkins_mock.call_args_list)
@patch.object(jenkins.Jenkins, 'jenkins_open')
def test_in_another_folder_failed(self, jenkins_mock):
jenkins_mock.side_effect = [
jenkins.JenkinsException()
]
with self.assertRaises(jenkins.JenkinsException) as context_manager:
self.j.rename_job(u'a Folder/TestJob', u'another Folder/TestJob_2')
self.assertEqual(
str(context_manager.exception),
('rename[a Folder/TestJob to another Folder/TestJob_2] failed, '
'source and destination folder must be the same'))
self._check_requests(jenkins_mock.call_args_list)

View File

@ -19,6 +19,18 @@ class JenkinsBuildConsoleTest(JenkinsTestBase):
u'http://example.com/job/Test%20Job/52/consoleText')
self._check_requests(jenkins_mock.call_args_list)
@patch.object(jenkins.Jenkins, 'jenkins_open')
def test_in_folder(self, jenkins_mock):
jenkins_mock.return_value = "build console output..."
build_info = self.j.get_build_console_output(u'a Folder/Test Job', number=52)
self.assertEqual(build_info, jenkins_mock.return_value)
self.assertEqual(
jenkins_mock.call_args[0][0].get_full_url(),
u'http://example.com/job/a%20Folder/job/Test%20Job/52/consoleText')
self._check_requests(jenkins_mock.call_args_list)
@patch.object(jenkins.Jenkins, 'jenkins_open')
def test_return_none(self, jenkins_mock):
jenkins_mock.return_value = None
@ -30,6 +42,17 @@ class JenkinsBuildConsoleTest(JenkinsTestBase):
'job[TestJob] number[52] does not exist')
self._check_requests(jenkins_mock.call_args_list)
@patch.object(jenkins.Jenkins, 'jenkins_open')
def test_in_folder_return_none(self, jenkins_mock):
jenkins_mock.return_value = None
with self.assertRaises(jenkins.JenkinsException) as context_manager:
self.j.get_build_console_output(u'A Folder/TestJob', number=52)
self.assertEqual(
str(context_manager.exception),
'job[A Folder/TestJob] number[52] does not exist')
self._check_requests(jenkins_mock.call_args_list)
@patch.object(jenkins.Jenkins, 'jenkins_open')
def test_return_invalid_json(self, jenkins_mock):
jenkins_mock.return_value = 'Invalid JSON'
@ -57,6 +80,25 @@ class JenkinsBuildConsoleTest(JenkinsTestBase):
'job[TestJob] number[52] does not exist')
self._check_requests(jenkins_mock.call_args_list)
@patch.object(jenkins.Jenkins, 'jenkins_open')
def test_in_folder_raise_HTTPError(self, jenkins_mock):
jenkins_mock.side_effect = jenkins.HTTPError(
'http://example.com/job/a%20Folder/job/TestJob/52/consoleText',
code=401,
msg="basic auth failed",
hdrs=[],
fp=None)
with self.assertRaises(jenkins.JenkinsException) as context_manager:
self.j.get_build_console_output(u'a Folder/TestJob', number=52)
self.assertEqual(
jenkins_mock.call_args[0][0].get_full_url(),
u'http://example.com/job/a%20Folder/job/TestJob/52/consoleText')
self.assertEqual(
str(context_manager.exception),
'job[a Folder/TestJob] number[52] does not exist')
self._check_requests(jenkins_mock.call_args_list)
class JenkinsBuildInfoTest(JenkinsTestBase):
@ -78,6 +120,24 @@ class JenkinsBuildInfoTest(JenkinsTestBase):
u'http://example.com/job/Test%20Job/52/api/json?depth=0')
self._check_requests(jenkins_mock.call_args_list)
@patch.object(jenkins.Jenkins, 'jenkins_open')
def test_in_folder(self, jenkins_mock):
build_info_to_return = {
u'building': False,
u'msg': u'test',
u'revision': 66,
u'user': u'unknown'
}
jenkins_mock.return_value = json.dumps(build_info_to_return)
build_info = self.j.get_build_info(u'a Folder/Test Job', number=52)
self.assertEqual(build_info, build_info_to_return)
self.assertEqual(
jenkins_mock.call_args[0][0].get_full_url(),
u'http://example.com/job/a%20Folder/job/Test%20Job/52/api/json?depth=0')
self._check_requests(jenkins_mock.call_args_list)
@patch.object(jenkins.Jenkins, 'jenkins_open')
def test_return_none(self, jenkins_mock):
jenkins_mock.return_value = None
@ -116,6 +176,22 @@ class JenkinsBuildInfoTest(JenkinsTestBase):
'job[TestJob] number[52] does not exist')
self._check_requests(jenkins_mock.call_args_list)
@patch.object(jenkins.Jenkins, 'jenkins_open')
def test_in_folder_raise_HTTPError(self, jenkins_mock):
jenkins_mock.side_effect = jenkins.HTTPError(
'http://example.com/job/a%20Folder/job/TestJob/api/json?depth=0',
code=401,
msg="basic auth failed",
hdrs=[],
fp=None)
with self.assertRaises(jenkins.JenkinsException) as context_manager:
self.j.get_build_info(u'a Folder/TestJob', number=52)
self.assertEqual(
str(context_manager.exception),
'job[a Folder/TestJob] number[52] does not exist')
self._check_requests(jenkins_mock.call_args_list)
class JenkinsStopBuildTest(JenkinsTestBase):
@ -128,6 +204,16 @@ class JenkinsStopBuildTest(JenkinsTestBase):
u'http://example.com/job/Test%20Job/52/stop')
self._check_requests(jenkins_mock.call_args_list)
@patch.object(jenkins.Jenkins, 'jenkins_open')
def test_in_folder(self, jenkins_mock):
self.j.stop_build(u'a Folder/Test Job', number=52)
self.assertEqual(
jenkins_mock.call_args[0][0].get_full_url(),
u'http://example.com/job/a%20Folder/job/Test%20Job/52/stop')
self._check_requests(jenkins_mock.call_args_list)
class JenkinsListRunningBuildsTest(JenkinsTestBase):
@patch.object(jenkins.Jenkins, 'get_node_info')

25
tests/test_job_folder.py Normal file
View File

@ -0,0 +1,25 @@
from mock import patch
import jenkins
from tests.base import JenkinsTestBase
class JenkinsGetJobFolderTest(JenkinsTestBase):
@patch.object(jenkins.Jenkins, 'jenkins_open')
def test_simple(self, jenkins_mock):
folder, name = self.j._get_job_folder('my job')
self.assertEqual(folder, '')
self.assertEqual(name, 'my job')
@patch.object(jenkins.Jenkins, 'jenkins_open')
def test_single_level(self, jenkins_mock):
folder, name = self.j._get_job_folder('my folder/my job')
self.assertEqual(folder, 'job/my folder/')
self.assertEqual(name, 'my job')
@patch.object(jenkins.Jenkins, 'jenkins_open')
def test_multi_level(self, jenkins_mock):
folder, name = self.j._get_job_folder('folder1/folder2/my job')
self.assertEqual(folder, 'job/folder1/job/folder2/')
self.assertEqual(name, 'my job')