#10 Implement Unit Testing. Closes #10

This commit is contained in:
2020-06-20 06:54:35 +00:00
parent 10d461bb54
commit 9b57394460
20 changed files with 1273 additions and 67 deletions

10
.gitignore vendored
View File

@ -20,4 +20,12 @@ pyvenv.cfg
#Don't include the autogenerated Module docs
docs/module/*
docs/includes/*
docs/includes/*
# Don't track JUnit Test or coverage Reports from unittests
*.JUnit.xml
.coverage*
*,cover
public/*
htmlcov/*
UnitTesting_coverage.xml

View File

@ -3,6 +3,8 @@
stages:
- Verify
- Unit Testing
- package
- build
- test
@ -11,20 +13,189 @@ stages:
- publish
.PythonImageBuildModuleBefore_Script: &PythonImageBuildModuleBefore_Script |
pip install --user --upgrade setuptools wheel
pip install -r requirements.txt
pip install -r test/requirements_unittest.pip
apt-get update && apt-get install -y --no-install-recommends git
git --version
GIT_PYTHON_GIT_EXECUTABLE=$(which git)
echo $GIT_PYTHON_GIT_EXECUTABLE
echo $PATH
PATH=$PATH:$GIT_PYTHON_GIT_EXECUTABLE
PyLint:
stage: Verify
image: python:3.6.9-slim
before_script:
- *PythonImageBuildModuleBefore_Script
- python3 setup.py egg_info sdist bdist_wheel
script:
- python3 -m pylint --exit-zero --output-format=pylint_gitlab.GitlabCodeClimateReporter gitlab_management test *.py > gl-code-quality-report.json
- python3 -m pylint --exit-zero --output-format=pylint_gitlab.GitlabPagesHtmlReporter gitlab_management test *.py > gl-code-quality-report.html
- PyPIScore=$(python3 -m pylint --exit-zero gitlab_management test *.py | sed -n 's/^Your code has been rated at \([-0-9./]*\).*/\1/p')
- |
echo "{
\"PyLintScore\": \"$PyPIScore\"
}
" > badge_pylint.json
rules:
- if: $CI_COMMIT_TAG
when: never
- if: $CI_MERGE_REQUEST_IID
when: never
- if: '$CI_COMMIT_BRANCH'
changes:
- gitlab_management/*.py
- test/*.py
- ./*.py
when: always
artifacts:
expire_in: 60 days
paths:
- gl-code-quality-report.json
- gl-code-quality-report.html
- badge_pylint.json
reports:
codequality: gl-code-quality-report.json
Unit Test:
stage: Unit Testing
image: python:3.6.9-slim
before_script:
- *PythonImageBuildModuleBefore_Script
- python3 setup.py egg_info sdist bdist_wheel
script:
- coverage run --parallel-mode --branch --context=Unit_Testing --source gitlab_management test/test_unit.py
rules:
- if: $CI_COMMIT_TAG
when: never
- if: $CI_MERGE_REQUEST_IID
when: never
- if: '$CI_COMMIT_BRANCH'
changes:
- gitlab_management/*.py
- test/test_*.py
- setup.py
when: always
artifacts:
expire_in: 3 days
paths:
- Unit.JUnit.xml
- gitlab_management/*.cover
- .coverage*
reports:
junit: Unit.JUnit.xml
Function Test:
stage: Unit Testing
image: python:3.6.9-slim
before_script:
- *PythonImageBuildModuleBefore_Script
- python3 setup.py egg_info sdist bdist_wheel
script:
- coverage run --parallel-mode --branch --context=Function_Testing --source gitlab_management test/test_function.py
rules:
- if: $CI_COMMIT_TAG
when: never
- if: $CI_MERGE_REQUEST_IID
when: never
- if: '$CI_COMMIT_BRANCH'
changes:
- gitlab_management/*.py
- test/test_*.py
- setup.py
when: always
artifacts:
expire_in: 3 days
paths:
- Function.JUnit.xml
- gitlab_management/*.cover
- .coverage*
reports:
junit: Function.JUnit.xml
Integration Test:
stage: Unit Testing
image: python:3.6.9-slim
before_script:
- *PythonImageBuildModuleBefore_Script
- python3 setup.py egg_info sdist bdist_wheel
script:
- coverage run --parallel-mode --branch --context=Integration_Testing --source gitlab_management test/test_integration.py
rules:
- if: $CI_COMMIT_TAG
when: never
- if: $CI_MERGE_REQUEST_IID
when: never
- if: '$CI_COMMIT_BRANCH'
changes:
- gitlab_management/*.py
- test/test_*.py
- setup.py
when: always
artifacts:
expire_in: 3 days
paths:
- Integration.JUnit.xml
- gitlab_management/*.cover
- .coverage*
reports:
junit: Integration.JUnit.xml
Coverage:
stage: package
image: python:3.6.9-slim
variables:
COVERAGE_DIR: public/$CI_COMMIT_BRANCH/coverage
before_script:
- *PythonImageBuildModuleBefore_Script
- python3 setup.py egg_info sdist bdist_wheel
script:
- coverage combine --append
- coverage report
- coverage html --show-contexts -d public/$CI_COMMIT_BRANCH/coverage
- coverage json -o badge_coverage.json
- coverage xml -o UnitTesting_coverage.xml
rules:
- if: $CI_COMMIT_TAG
when: never
- if: $CI_MERGE_REQUEST_IID
when: never
- if: '$CI_COMMIT_BRANCH'
changes:
- gitlab_management/*.py
- setup.py
- test/*.py
when: on_success
dependencies:
- Unit Test
- Function Test
- Integration Test
artifacts:
expire_in: 60 days
when: on_success
paths:
- UnitTesting_coverage.xml
- public/*
- badge_coverage.json
reports:
cobertura: UnitTesting_coverage.xml
gitlab-management_package:
stage: package
image: python:3.6.9-slim
variables:
GIT_PYTHON_GIT_EXECUTABLE: /bin/git
before_script:
- apt-get update && apt-get install -y --no-install-recommends git
- *PythonImageBuildModuleBefore_Script
- git --version
- GIT_PYTHON_GIT_EXECUTABLE=$(which git)
- echo $GIT_PYTHON_GIT_EXECUTABLE
- echo $PATH
- PATH=$PATH:$GIT_PYTHON_GIT_EXECUTABLE
- python3 -m pip install --user --upgrade setuptools wheel
- python3 -m pip install -r requirements.txt
script:
- python3 setup.py egg_info sdist bdist_wheel
rules:
@ -38,7 +209,7 @@ gitlab-management_package:
- setup.py
- README.md
- CONTRIBUTING.md
when: always
when: on_success
artifacts:
expire_in: 3 days
when: on_success
@ -85,6 +256,31 @@ include:
- template: SAST.gitlab-ci.yml
pages:
stage: test
dependencies:
- Coverage
script:
- echo coverage reports to gitlab pages
artifacts:
paths:
- public
expire_in: 3 days
rules:
- if: $CI_COMMIT_TAG
when: never
- if: $CI_MERGE_REQUEST_IID
when: never
- if: '$CI_COMMIT_BRANCH'
changes:
- gitlab_management/*.py
- setup.py
- README.md
- CONTRIBUTING.md
when: on_success
variables:
SAST_DEFAULT_ANALYZERS: "bandit"
@ -134,7 +330,7 @@ container_scanning:
- if: $CI_COMMIT_BRANCH
when: never
gemnasium-python-dependency_scanning:
variables:
DS_PYTHON_VERSION: 3
@ -151,8 +347,16 @@ gemnasium-python-dependency_scanning:
changes:
- gitlab_management/*.py
- setup.py
- requirements.txt
- requirements.pip
when: on_success
allow_failure: false
artifacts:
paths:
- gl-dependency-scanning-report.json
reports:
dependency_scanning: gl-dependency-scanning-report.json
# to activate licence approvals: https://docs.gitlab.com/ee/user/application_security/#enabling-license-approvals-within-a-project
license_scanning:

View File

@ -48,6 +48,49 @@ python3 setup.py sdist bdist_wheel
>**Note:** you must increment the version in the build script (`buildinit.py`) prior to committing your final changes to the repo.
## running builds
Prior to committing to the repo, test builds need to be conducted. we have designed this to replicate gitlabs CI. Each stage of the `.gitlab-ci.yml` can be run from the command line using the following docker command
``` bash
docker run --privileged -w $PWD -v $PWD:$PWD -v /var/run/docker.sock:/var/run/docker.sock gitlab/gitlab-runner:ubuntu-v13.0.0 exec docker ${Gitlab-CI Stage} -env "CI_COMMIT_BRANCH=$(git rev-parse --abbrev-ref HEAD)" --docker-privileged --docker-volumes '/var/run/docker.sock:/var/run/docker.sock'
```
>**tip:** substitute `{Gitlab-CI Stage}` with one of the available stages below before running this command
Tested and confirmed `.gitlab-ci.yml` tasks as working with the above command
- **Verify**
* PyLint
- **Unit Testing**
* Unit Test
* Function Test
- **package**
* Coverage *- Not Working*
* gitlab-management_package
- **build**
* ~~Docker_Build-Alpine~~ *- not working*
- **test**
* ~~pages~~ *- Only usable on GitLab*
* ~~bandit-sast~~ *- Not Tested*
* ~~Scan gitlab-management-Alpine~~ *- Not Tested*
* ~~gemnasium-python-dependency_scanning~~ *- Not Tested*
* ~~license_scanning~~ *- Not Tested*
- **validate**
* ~~Documentation~~ *- Not Working*
**Tip:** use this command to build/test the docs
```bash
cd {Repo Directory}
rm -Rf build bin docs/_build gitlab_management.egg-info lib include pyvenv.cfg
CI_PROJECT_DIR=/Repository && docker run -e CI_PROJECT_DIR=$CI_PROJECT_DIR -w $CI_PROJECT_DIR -v $PWD:$CI_PROJECT_DIR readthedocs/build:latest bash test/validation-build-docs.sh
```
- **release**
* ~~GitLab-Release~~ *- Only usable on GitLab*
- **publish**
* ~~Publish~~ *- Only usable on GitLab*
### Version Changes
Every change, prior to being committed to the `development` branch, must have it's version incremented. In most cases only the `{newfeature}` number will need to be incremented.
@ -171,11 +214,6 @@ Clean-up the environment prior to building the documentation, with:
rm -Rf build bin docs/_build gitlab_management.egg-info lib include pyvenv.cfg
```
To build the documentation run:
``` bash
cd {Repo Directory}
CI_PROJECT_DIR=/Repository && docker run -e CI_PROJECT_DIR=$CI_PROJECT_DIR -w $CI_PROJECT_DIR -v $PWD:$CI_PROJECT_DIR readthedocs/build:latest bash test/validation-build-docs.sh
```
>**Tip:**

View File

@ -1,12 +1,22 @@
# Python Gitlab Management
[![Pipelien Status - Stable](https://img.shields.io/badge/dynamic/json.svg?label=Pipeline-Stable&query=0.status&url=https://gitlab.com/api/v4/projects/19099644/pipelines?ref=master&color=ff782e&logo=gitlab&style=plastic)](https://gitlab.com/nofusscomputing/projects/python-gitlab-management/)
[![Pipelien Status - Dev](https://img.shields.io/badge/dynamic/json.svg?label=Pipeline-Dev&query=0.status&url=https://gitlab.com/api/v4/projects/19099644/pipelines/?ref=development&color=ff782e&logo=gitlab&style=plastic)](https://gitlab.com/nofusscomputing/projects/python-gitlab-management/)
![PyPi Version](https://img.shields.io/badge/dynamic/json?label=PyPi%20Package&query=$.info.version&url=https://pypi.org/pypi/gitlab-management/json&logo=python&logoColor=white&style=plastic)
[![PyPi Version](https://img.shields.io/badge/dynamic/json?label=PyPi%20Package&query=$.info.version&url=https://pypi.org/pypi/gitlab-management/json&logo=pypi&logoColor=white&style=plastic)](https://pypi.org/project/gitlab-management/)
[![License: LGPL v3](https://img.shields.io/badge/License-LGPL%20v3-green.svg?style=plastic&logo=gnu&logocolor=white)](https://gitlab.com/nofusscomputing/projects/python-gitlab-management/-/blob/master/LICENCE)
[![Pipelien Status - Stable](https://img.shields.io/badge/dynamic/json.svg?label=Pipeline%20%5Bstable%5D&query=0.status&url=https://gitlab.com/api/v4/projects/19099644/pipelines?ref=master&color=ff782e&logo=gitlab&style=plastic)](https://gitlab.com/nofusscomputing/projects/python-gitlab-management/)
[![Code Coverage - Stable](https://img.shields.io/badge/dynamic/json?color=F01F7A&label=Coverage%20%5Bstable%5D&query=%24.totals.percent_covered&suffix=%&logo=codecov&style=plastic&url=https%3A%2F%2Fnofusscomputing.gitlab.io%2Fprojects%2Fpython-gitlab-management%2Fmaster%2Fcoverage.json)](https://nofusscomputing.gitlab.io/projects/python-gitlab-management/master/coverage/)
[![PyLint Score Master](https://img.shields.io/badge/dynamic/json?color=73398D&label=PyLint%20Score%20%5Bstable%5D&query=%24.PyLintScore&style=plastic&url=https:%2F%2Fgitlab.com%2Fnofusscomputing%2Fprojects%2Fpython-gitlab-management%2F-%2Fjobs%2Fartifacts%2Fmaster%2Ffile%2Fbadge_pylint.json?job=PyLint)](https://gitlab.com/nofusscomputing/projects/python-gitlab-management/-/jobs/artifacts/master/file/gl-code-quality-report.html?job=PyLint)
[![Read the Docs (version)](https://img.shields.io/readthedocs/python-gitlab-management/stable?label=Docs%20stable&logo=readthedocs&style=plastic)](https://python-gitlab-management.readthedocs.io/en/stable/)
[![Pipelien Status - Dev](https://img.shields.io/badge/dynamic/json.svg?label=Pipeline%20%5BDev%5D&query=0.status&url=https://gitlab.com/api/v4/projects/19099644/pipelines/?ref=development&color=ff782e&logo=gitlab&style=plastic)](https://gitlab.com/nofusscomputing/projects/python-gitlab-management/)
[![Code Coverage - dev](https://img.shields.io/badge/dynamic/json?color=F01F7A&label=Coverage%20%5BDev%5D&query=%24.totals.percent_covered&suffix=%&logo=codecov&style=plastic&url=https://gitlab.com/nofusscomputing/projects/python-gitlab-management/-/jobs/artifacts/development/file/badge_coverage.json?job=Coverage)](https://nofusscomputing.gitlab.io/projects/python-gitlab-management/development/coverage/)
[![PyLint Score Dev](https://img.shields.io/badge/dynamic/json?color=73398D&label=PyLint%20Score%20%5BDev%5D&query=%24.PyLintScore&style=plastic&url=https://gitlab.com/nofusscomputing/projects/python-gitlab-management/-/jobs/artifacts/development/file/badge_pylint.json?job=PyLint)](https://gitlab.com/nofusscomputing/projects/python-gitlab-management/-/jobs/artifacts/development/file/gl-code-quality-report.html?job=PyLint)
[![Read the Docs (version)](https://img.shields.io/readthedocs/python-gitlab-management/development?label=Docs%20devel&logo=readthedocs&style=plastic)](https://python-gitlab-management.readthedocs.io/en/development/)
Gitlab-management is python module that enables GitLab group configuration from code. By design it's intended to be setup to run on a schedule.
## How to Use

View File

@ -15,7 +15,7 @@ __license__ = "GNU LGPLv3"
__copyright__ = "(C) All Rights reserved"
__source__ = ''
__title__ = "gitlab-management"
__version__ = "0.1.4"
__version__ = "0.1.5"
__doc__ = "https://gitlab.com/nofusscomputing/projects/python-gitlab-management"
CurrentDirectory = './'

View File

@ -8,4 +8,5 @@ For assistance in using this module please see the sections below.
configuration
docker
labels
labels
unittesting

26
docs/pages/unittesting.md Normal file
View File

@ -0,0 +1,26 @@
# Unit Testing
Unit testing is done on all classes and methods. Unit testing has been broken down into the following categories
1. Unit
1. Function
1. Integration
Depending on what is type of item is being tested will depend on where the testing code will go.
## Unit
The Unit test is the most basic of all of the tests. It's intent is to check the following items:
1. Method exists `{Classname}.globals`
1. all Inputs
1. The retrun values
1. Exceptions
* Unexpected
* Expected exceptions are thrown
whilst unit testing, all other methods or classes that the tested method is calling are to be mocked.
## Function
Function testing is intended to test interoperability between classes and methods, including any user input(s). Function testing is to be limited to required dependancies.
The only items that are to be mocked dueing function testing are any methods that require interaction outside of the tested class methods, including dependant modules. *i.e. a http request.*
## Integration
Integration testing is designed to confirm the classes, dependent modules and any required external services work as they were designed. If all tests upto and including this stage of testing pass, it is assumed that the module is ready for release.

View File

@ -1,3 +1,3 @@
recommonmark
m2r
python-gitlab
python-gitlab==2.2.0

View File

@ -8,20 +8,68 @@ class GitlabManagement:
No Fuss Computing's Gitlab Config Management python module.
"""
import gitlab as GitLabAPIWrapper
import gitlab
import gitlab.v4.objects
import traceback
import logging
from enum import Enum
GitlabSession:GitLabAPIWrapper.Gitlab = None
_GitlabSession:gitlab.Gitlab = None
DesiredOutputLevel:int = None
@property
def GitlabSession(self) -> gitlab.Gitlab:
if self._GitlabSession == None:
Config:dict = None
return None
else:
return self._GitlabSession
@GitlabSession.setter
def GitlabSession(self, oSession):
if type(oSession) is self.gitlab.Gitlab or type(oSession) is None:
self._GitlabSession = oSession
else:
raise TypeError("GitlabSession can only be type 'None' or 'gitlab.Gitlab' [{}] is neither".format(str(oSession)))
_DesiredOutputLevel:int = None
@property
def DesiredOutputLevel(self) -> int:
return self._DesiredOutputLevel
@DesiredOutputLevel.setter
def DesiredOutputLevel(self, Object):
if type(Object) is not self.OutputSeverity:
raise TypeError('{} must be of type OutputSeverity'.format(str(Object)))
self._DesiredOutputLevel = Object
_Config:dict = None
@property
def Config(self):
if self._Config == None:
if self.GetConfig():
return self._Config
else:
raise RuntimeError('Unable to get config')
else:
return self._Config
class OutputSeverity(Enum):
@ -29,26 +77,45 @@ class GitlabManagement:
Alert:int = 1
Critical:int = 2
Error:int = 3
Warnin:int = 4
Warning:int = 4
Notice:int = 5
Informational:int = 6
Debug:int = 7
GitlabObjectCache:dict = None
_GitlabObjectCache:dict = None
"""Cache the objects fetched via the GitLab API"""
@property
def GitlabObjectCache(self) -> dict:
"""Cache the objects fetched via the GitLab API
See Also
--------
`Dependent Methods`:
Nil
`issue #8 <https://gitlab.com/nofusscomputing/projects/python-gitlab-management/-/issues/8>`_
Feature: Groups now cached
"""
return self._GitlabObjectCache
def __init__(self, GitLab_URL:str, GitLab_PrivateToken:str):
def __init__(self, GitLab_URL:str, GitLab_PrivateToken:str, Authenticate:bool = True):
self.DesiredOutputLevel = self.OutputSeverity.Informational
if self.GitlabLoginAuthenticate(GitLab_URL, GitLab_PrivateToken):
if not self.GetConfig():
if not self.GitlabLoginAuthenticate(GitLab_URL, GitLab_PrivateToken, Authenticate):
self.Output(self.OutputSeverity.Critical, 'could not logon to ' + GitLab_URL)
if not self.GetConfig():
self.Output(self.OutputSeverity.Critical, "Couldn't load config yml")
else:
else:
self.Output(self.OutputSeverity.Notice, 'config loaded')
@ -56,15 +123,10 @@ class GitlabManagement:
CacheINIT['Groups']:dict = None
self.GitlabObjectCache = CacheINIT
else:
self.Output(self.OutputSeverity.Critical, 'could not logon to ' + GitLab_URL)
self._GitlabObjectCache = CacheINIT
def GitlabLoginAuthenticate(self, URL:str, PrivateToken:str) -> bool:
def GitlabLoginAuthenticate(self, URL:str, PrivateToken:str, Authenticate:bool = True) -> bool:
"""
Establish the Gitlab instance to connect to and authenticate.
@ -80,7 +142,7 @@ class GitlabManagement:
Raises
------
GitLabAPIWrapper.GitlabAuthenticationError
gitlab.GitlabAuthenticationError
Returns text output of the failed authentication issue that occured when attemping to authenticate.
Exception
@ -101,19 +163,18 @@ class GitlabManagement:
GitlabLoginAuthenticate = False
try:
GitlabLogin = self.GitLabAPIWrapper.Gitlab(URL, private_token=PrivateToken)
import gitlab_management
GitlabLogin.headers['User-Agent'] = "%s/%s %s" % (gitlab_management.__title__, gitlab_management.__version__, gitlab_management.__doc__)
GitlabLogin = self.gitlab.Gitlab(URL, private_token=PrivateToken)
GitlabLogin.headers['User-Agent'] = "%s/%s %s" % (self.get_detail('title'), self.get_detail('version'), self.get_detail('doc'))
GitlabLogin.auth()
if Authenticate:
GitlabLogin.auth()
self.GitlabSession = GitlabLogin
GitlabLoginAuthenticate = True
except self.GitLabAPIWrapper.GitlabAuthenticationError as e:
except self.gitlab.GitlabAuthenticationError as e:
self.Output(self.OutputSeverity.Error, str(e))
@ -196,12 +257,12 @@ class GitlabManagement:
try:
self.Config = dict(yaml.safe_load(stream))
self._Config = dict(yaml.safe_load(stream))
GetConfig = True
except yaml.YAMLError as exc:
self.Output(self.OutputSeverity.Error, exc)
self.Output(self.OutputSeverity.Error, str(exc))
except Exception as e:
self.Output(self.OutputSeverity.Critical, logging.error(traceback.format_exc()))
@ -231,10 +292,10 @@ class GitlabManagement:
Exception
Generic catch all Exception is returned, however the exception will be printed to the console
GitLabAPIWrapper.GitlabHttpError
gitlab.GitlabHttpError
A http error occured, the output should denote what the issue is
GitLabAPIWrapper.GitlabCreateError
gitlab.GitlabCreateError
a check for '409: label exists', as no attempt should be made to create a label when the check has already been done.
Warning
@ -258,7 +319,7 @@ class GitlabManagement:
NewLabelString = {}
CreateGroupLabel = False
if type(Group) == self.GitLabAPIWrapper.v4.objects.Group:
if type(Group) == self.gitlab.v4.objects.Group:
try:
@ -278,11 +339,11 @@ class GitlabManagement:
CreateGroupLabel = True
except self.GitLabAPIWrapper.GitlabHttpError as e:
except self.gitlab.GitlabHttpError as e:
self.Output(self.OutputSeverity.Critical, logging.error(traceback.format_exc()))
except self.GitLabAPIWrapper.GitlabCreateError as e:
except self.gitlab.GitlabCreateError as e:
if e.response_code == 409:
@ -346,7 +407,7 @@ class GitlabManagement:
if self.GitlabObjectCache['Groups'] is None:
Groups = self.GitlabSession.groups.list(min_access_level = self.GitLabAPIWrapper.MAINTAINER_ACCESS)
Groups = self.GitlabSession.groups.list(min_access_level = self.gitlab.MAINTAINER_ACCESS)
self.GitlabObjectCache['Groups'] = {}
@ -431,7 +492,7 @@ class GitlabManagement:
try:
if type(Group) is self.GitLabAPIWrapper.v4.objects.Group:
if type(Group) is self.gitlab.v4.objects.Group:
CachedGroupLabels = self.GitlabObjectCache['Groups'][str(Group.attributes['id'])]['Labels']
@ -544,3 +605,10 @@ class GitlabManagement:
self.Output(self.OutputSeverity.Critical, logging.error(traceback.format_exc()))
return ProcessConfigGroups
def get_detail(self, ItemName:str):
with open("gitlab_management/__init__.py") as f:
for line in f:
if line.startswith("__" + ItemName + "__"):
return(str(line.split("=")[-1]).replace('"', '').replace("\n", '').replace(" ", ''))

View File

@ -21,20 +21,23 @@ Example: python3 gitlab-management -T {GitlabAuthToken}
"""
def main():
try:
full_cmd_arguments = sys.argv
if len(full_cmd_arguments) == 0:
raise getopt.error('No Arguments specified')
argument_list = full_cmd_arguments[1:]
short_options = "H:T:hv:l"
long_options = ["Host=", "Token=" "help", "verbose=", "Labels"]
arguments, values = getopt.getopt(argument_list, short_options, long_options)
GitlabHost = None
GitlabPrivateToken = None
RunCLI = True
@ -63,7 +66,6 @@ def main():
# Setting the value to non-int will cause an un-caught exception
Verbose = current_value
print('here: ' + str(type(current_value)))
elif current_argument in ("-h", "--help"):
@ -88,7 +90,10 @@ def main():
if Option_Labels:
GitlabManagementObject.ProcessConfigLabels(GitlabManagementObject.Config['Group']['Labels'])
else:
sys.exit(3)
except getopt.error as err:
@ -102,7 +107,7 @@ def main():
except Exception as e:
print(logging.error(traceback.format_exc()))
sys.exit(2)

View File

@ -1,2 +1,3 @@
python-gitlab
gitpython
python-gitlab==2.2.0
GitPython==3.1.3
PyYAML==5.3.1

View File

@ -13,7 +13,7 @@ def get_detail(ItemName:str):
for line in f:
if line.startswith("__" + ItemName + "__"):
return(str(line.split("=")[-1]).replace('"', '').replace("\n", '').replace(" ", ''))
setuptools.setup(
name=get_detail('title'),
version=get_detail('version'),
@ -31,8 +31,9 @@ setuptools.setup(
},
packages=setuptools.find_packages(),
install_requires=[
'python-gitlab',
'PyYAML'
'GitPython==3.1.3',
'python-gitlab==2.2.0',
'PyYAML==5.3.1'
],
entry_points = {
'console_scripts': ['gitlab-management=gitlab_management.cli:main'],

1
test/__init__.py Normal file
View File

@ -0,0 +1 @@

View File

@ -0,0 +1,173 @@
[
{
"id": 1,
"name": "Bug",
"color": "#FF0000",
"description": "Used in discussion about a bug in comments or commits",
"description_html": "Used in discussion about a bug in comments or commits",
"text_color": "#FFFFFF",
"subscribed": false
},
{
"id": 2,
"name": "Bug::Reproducable",
"color": "#FF0000",
"description": "Given to an issue when the bug has been confirmed as re-producable. Can also be used in comments and commits.",
"description_html": "Given to an issue when the bug has been confirmed as re-producable. Can also be used in comments and commits.",
"text_color": "#FFFFFF",
"subscribed": false
},
{
"id": 3,
"name": "Bug::Unable to Reproduce",
"color": "#FF0000",
"description": "Given to an issue when the bug that is not able to be reproduced. Can also be used in comments and commits.",
"description_html": "Given to an issue when the bug that is not able to be reproduced. Can also be used in comments and commits.",
"text_color": "#FFFFFF",
"subscribed": false
},
{
"id": 4,
"name": "Category::Bug",
"color": "#A295D6",
"description": "Category for Issues and Merge Requests. Can also be used in discussion in comments and commits",
"description_html": "Category for Issues and Merge Requests. Can also be used in discussion in comments and commits",
"text_color": "#333333",
"subscribed": false
},
{
"id": 5,
"name": "Category::Feature",
"color": "#A295D6",
"description": "Category for Issues and Merge Requests. Can also be used in discussion in comments and commits",
"description_html": "Category for Issues and Merge Requests. Can also be used in discussion in comments and commits",
"text_color": "#333333",
"subscribed": false
},
{
"id": 6,
"name": "Code Review::Complete",
"color": "#D9534F",
"description": null,
"description_html": "",
"text_color": "#FFFFFF",
"subscribed": false
},
{
"id": 7,
"name": "Code Review::Not Started",
"color": "#D9534F",
"description": null,
"description_html": "",
"text_color": "#FFFFFF",
"subscribed": false
},
{
"id": 8,
"name": "Code Review::Rejected",
"color": "#D9534F",
"description": null,
"description_html": "",
"text_color": "#FFFFFF",
"subscribed": false
},
{
"id": 9,
"name": "Code Review::Underway",
"color": "#D9534F",
"description": null,
"description_html": "",
"text_color": "#FFFFFF",
"subscribed": false
},
{
"id": 10,
"name": "Documentation",
"color": "#428BCA",
"description": "Documentation items",
"description_html": "Documentation items",
"text_color": "#FFFFFF",
"subscribed": false
},
{
"id": 11,
"name": "Documentation::Complete",
"color": "#428BCA",
"description": "Issues, Merge Requests",
"description_html": "Issues, Merge Requests",
"text_color": "#FFFFFF",
"subscribed": false
},
{
"id": 12,
"name": "Documentation::No Change Required",
"color": "#428BCA",
"description": "Issues, Merge Requests and used to denote no documentation changes required",
"description_html": "Issues, Merge Requests and used to denote no documentation changes required",
"text_color": "#FFFFFF",
"subscribed": false
},
{
"id": 13,
"name": "Documentation::Not Started",
"color": "#428BCA",
"description": "Issues, Merge Requests",
"description_html": "Issues, Merge Requests",
"text_color": "#FFFFFF",
"subscribed": false
},
{
"id": 14,
"name": "Documentation::Stalled",
"color": "#428BCA",
"description": "Issues, Merge Requests",
"description_html": "Issues, Merge Requests",
"text_color": "#FFFFFF",
"subscribed": false
},
{
"id": 15,
"name": "Documentation::Underway",
"color": "#428BCA",
"description": "Issues, Merge Requests",
"description_html": "Issues, Merge Requests",
"text_color": "#FFFFFF",
"subscribed": false
},
{
"id": 16,
"name": "Feature",
"color": "#A295D6",
"description": "Used in discussion about a Feature in comments or commits",
"description_html": "Used in discussion about a Feature in comments or commits",
"text_color": "#333333",
"subscribed": false
},
{
"id": 17,
"name": "stage::develop",
"color": "#004E00",
"description": "Stage for use in Issues and Merge Requests. Can also be used in discussion in comments and commits",
"description_html": "Stage for use in Issues and Merge Requests. Can also be used in discussion in comments and commits",
"text_color": "#FFFFFF",
"subscribed": false
},
{
"id": 18,
"name": "stage::planning",
"color": "#004E00",
"description": "Stage for use in Issues and Merge Requests. Can also be used in discussion in comments and commits",
"description_html": "Stage for use in Issues and Merge Requests. Can also be used in discussion in comments and commits",
"text_color": "#FFFFFF",
"subscribed": false
},
{
"id": 19,
"name": "stage::test",
"color": "#004E00",
"description": "Stage for use in Issues and Merge Requests. Can also be used in discussion in comments and commits",
"description_html": "Stage for use in Issues and Merge Requests. Can also be used in discussion in comments and commits",
"text_color": "#FFFFFF",
"subscribed": false
}
]

View File

@ -0,0 +1,56 @@
[
{
"id": 1,
"web_url": "http://mock_gitlab_url/groups/documents",
"name": "Documents",
"path": "documents",
"description": "Documents",
"visibility": "private",
"share_with_group_lock": false,
"require_two_factor_authentication": false,
"two_factor_grace_period": 48,
"project_creation_level": "developer",
"auto_devops_enabled": null,
"subgroup_creation_level": "maintainer",
"emails_disabled": null,
"mentions_disabled": null,
"lfs_enabled": true,
"default_branch_protection": 2,
"avatar_url": null,
"request_access_enabled": true,
"full_name": "Documents",
"full_path": "documents",
"created_at": "2020-05-10T03:59:02.143Z",
"parent_id": 0,
"ldap_cn": null,
"ldap_access": null
},
{
"id": 2,
"web_url": "https://mock_gitlab_url/groups/projects",
"name": "Projects",
"path": "projects",
"description": "Projects",
"visibility": "public",
"share_with_group_lock": false,
"require_two_factor_authentication": false,
"two_factor_grace_period": 48,
"project_creation_level": "developer",
"auto_devops_enabled": null,
"subgroup_creation_level": "maintainer",
"emails_disabled": null,
"mentions_disabled": null,
"lfs_enabled": true,
"default_branch_protection": 2,
"avatar_url": null,
"request_access_enabled": true,
"full_name": "Projects",
"full_path": "projects",
"created_at": "2020-05-31T10:41:37.648Z",
"parent_id": 0,
"ldap_cn": null,
"ldap_access": null
}
]

View File

@ -13,3 +13,5 @@ readthedocs-sphinx-ext<1.1
m2r==0.2.1
sphinxcontrib-apidoc==0.3.0

View File

@ -0,0 +1,6 @@
unittest-xml-reporting
coverage
requests_mock==1.8.0
pylint==2.5.2
pylint-gitlab==0.1.0

165
test/test_function.py Normal file
View File

@ -0,0 +1,165 @@
#!/usr/bin/env python3
#-*- coding: utf-8 -*-
import sys
sys.path.insert(0, "../gitlab_management")
sys.path.insert(0, "./gitlab_management")
sys.path.insert(0, "./")
from base import GitlabManagement
import unittest, xmlrunner
from enum import Enum
import gitlab
import gitlab_management
from cli import main as cli
from unittest.mock import patch
class GitlabManagement_Functional(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.ClassToTest = GitlabManagement
cls.Gitlab_Instance_URL = 'http://gitlab.com'
cls.Gitlab_Instance_TOKEN = 'Token'
ConfigFileContents = """Group:
Labels:
-
Group: "ExampleGroup1"
Name: "Stage::Planning"
Description: "example description for the label"
Color: "#FF0000"
-
Group:
- "ExampleGroup1"
- "ExampleGroup2"
Name: "Stage::RequireFeedback"
Description: "example description for the label"
Color: "#FF0000"
"""
#create config.yml
with open('config.yml', 'w') as ConfigFile:
ConfigFile.write(ConfigFileContents)
cls.ClassToTest = GitlabManagement(cls.Gitlab_Instance_URL, cls.Gitlab_Instance_TOKEN, False)
def test_cli_NoArgs(self):
#No args should display help and exit non-zero to denotee error
with self.assertRaises(SystemExit, msg="When cli exits, an `ExitCode` must be present") as ExitCode:
with unittest.mock.patch('sys.argv', []):
cli()
self.assertEqual(ExitCode.exception.code, 1, msg="noargs should exit 1")
def test_cli_Help(self):
with self.assertRaises(SystemExit, msg="When cli exits, an `ExitCode` must be present") as ExitCode:
with unittest.mock.patch('sys.argv', ['DummyFileName', "-h"]):
cli()
self.assertEqual(ExitCode.exception.code, 0, msg="Help should exit on 0")
# ToDo assert output matches
def test_cliLogonSuccess(self):
with unittest.mock.patch('gitlab_management.base.GitlabManagement.GitlabLoginAuthenticate') as mock_authenticate:
# Set auth to true, as this method is to only return bool
mock_authenticate.return_value = True
with unittest.mock.patch('gitlab_management.base.GitlabManagement._GitlabSession', unittest.mock.Mock()) as Session:
Session = 'NotNoneMeansAuthenticated'
#Mock Config Labels
with unittest.mock.patch('gitlab_management.base.GitlabManagement.ProcessConfigLabels', unittest.mock.Mock()):
# specify all parameters for a connection, including host, verbose 3 and process labels, should exit 0
with unittest.mock.patch('sys.argv', ['DummyFileName', '-H', 'http://127.0.0.1', '-T', 'invalidtoken', '-v', '3', '-l']):
with self.assertRaises(SystemExit, msg="When cli exits, an `ExitCode` must be present") as ExitCode:
cli()
self.assertEqual(ExitCode.exception.code, 0, msg="connection should exit with code 0")
# same test as above, however, don't specify host and http://gitlab.com will be used.
with unittest.mock.patch('sys.argv', ['DummyFileName', '-T', 'invalidtoken', '-v', '3']):
with self.assertRaises(SystemExit, msg="When cli exits, an `ExitCode` must be present") as ExitCode:
cli()
self.assertEqual(ExitCode.exception.code, 0, msg="connection should exit with code 0")
# same test as above, however, don't specify host and http://gitlab.com will be used and verbose above max of 7.
with unittest.mock.patch('sys.argv', ['DummyFileName', '-T', 'invalidtoken', '-v', '8', '-l']):
with self.assertRaises(SystemExit, msg="When cli exits, an `ExitCode` must be present") as ExitCode:
cli()
self.assertEqual(ExitCode.exception.code, 0, msg="connection should exit with code 0")
# Test a failed authenticate.
with unittest.mock.patch('gitlab.Gitlab') as GitLabNone:
GitLabNone.return_value = None
mock_authenticate.return_value = False
with unittest.mock.patch('sys.argv', ['DummyFileName', '-T', 'invalidtoken', '-v', '8', '-l']):
with self.assertRaises(SystemExit, msg="When cli exits, an `ExitCode` must be present") as ExitCode:
cli()
self.assertNotEqual(ExitCode.exception.code, 0, msg="failed login should exit non zero")
@patch('gitlab_management.base.GitlabManagement.GitlabLoginAuthenticate')
def test_cli_LogonFail(self, mock_authenticate):
#self.skipTest('ToDo: Implement an exit code for failed logon')
# Set auth to true, as this method is to only return bool
mock_authenticate.return_value = False
# specify all parameters for a connection, including host, verbose 3 and process labels, should exit 0
with self.assertRaises(SystemExit, msg="When cli exits, an `ExitCode` must be present") as ExitCode:
with unittest.mock.patch('sys.argv', ['DummyFileName', '-H', 'http://127.0.0.1', '-T', 'invalidtoken']):
cli()
#ToDo Assert output to notify user
self.assertNotEqual(ExitCode.exception.code, 0, msg="connection should exit with code non zero")
@patch('gitlab_management.base.GitlabManagement.GitlabLoginAuthenticate')
def test_cliProcessLabels(self, mock_authenticate):
# Set auth to true, as this method is to only return bool
mock_authenticate.return_value = True
# specify all parameters for a connection, including host, verbose 3 and process labels, should exit 0
with self.assertRaises(SystemExit, msg="When cli exits, an `ExitCode` must be present") as ExitCode:
with unittest.mock.patch('sys.argv', ['DummyFileName', '-H', 'http://127.0.0.1', '-T', 'invalidtoken', '-l']):
with unittest.mock.patch('gitlab_management.base.GitlabManagement.GitlabSession', unittest.mock.Mock()): # Must be a session available, so mock it
with unittest.mock.patch('gitlab_management.base.GitlabManagement.ProcessConfigLabels', unittest.mock.Mock()): #ToDo, change this to patch decoration so that the return can be set as the cli should output success/failure and exitcode to match
cli()
self.assertEqual(ExitCode.exception.code, 0, msg="connection should exit with code 0")
@classmethod
def tearDownClass(cls):
pass
if __name__ == '__main__':
with open('Function.JUnit.xml', 'wb') as output:
unittest.main(
testRunner=xmlrunner.XMLTestRunner(output=output),
failfast=False, buffer=False, catchbreak=False)

108
test/test_integration.py Normal file
View File

@ -0,0 +1,108 @@
#!/usr/bin/env python3
#-*- coding: utf-8 -*-
import os, sys
sys.path.insert(0, "../gitlab_management")
sys.path.insert(0, "./gitlab_management")
sys.path.insert(0, "./")
from base import GitlabManagement
import unittest, xmlrunner
from unittest.mock import patch
import enum
import gitlab
import requests_mock
import gitlab_management
import yaml
class GitlabManagement_Unit(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.ConfigFileContents = """Group:
Labels:
-
Group: "ExampleGroup1"
Name: "Stage::Planning"
Description: "example description for the label"
Color: "#FF0000"
-
Group:
- "ExampleGroup1"
- "ExampleGroup2"
Name: "Stage::RequireFeedback"
Description: "example description for the label"
Color: "#FF0000"
"""
cls.ConfigFileContents_InvalidYML = """Group:
Labels:
-
Group "This Group label is missing the ':' so it's invalid"
Name: "Stage::Planning"
Description: "example description for the label"
Color: "#FF0000"
-
Group:
- "ExampleGroup1"
- "ExampleGroup2"
Name: "Stage::RequireFeedback"
Description: "example description for the label"
Color: "#FF0000"
"""
cls.Gitlab_Instance_URL = 'http://mock_gitlab_url'
cls.Gitlab_Instance_TOKEN = 'Gitlab token'
with open('config.yml', 'w') as ConfigFile:
ConfigFile.write(cls.ConfigFileContents)
ConfigFile.close()
# login and class init needs to be done for each test
#cls.ClassToTest = GitlabManagement(cls.Gitlab_Instance_URL, cls.Gitlab_Instance_TOKEN, False)
#The integration tests will be done against a gitlab instance
def test_CreateGroupLabel(self):
self.skipTest('implement')
def test_GetGroupByName(self):
self.skipTest('implement')
def test_GetLabelByName(self):
self.skipTest('implement')
def test_GitlabLoginAuthenticate(self):
self.skipTest('implement')
@classmethod
def tearDownClass(cls):
pass
if __name__ == '__main__':
#unittest.main()
with open('Integration.JUnit.xml', 'wb') as output:
unittest.main(
testRunner=xmlrunner.XMLTestRunner(output=output),
failfast=False, buffer=False, catchbreak=False)

333
test/test_unit.py Normal file
View File

@ -0,0 +1,333 @@
#!/usr/bin/env python3
#-*- coding: utf-8 -*-
import os, sys
sys.path.insert(0, "../gitlab_management")
sys.path.insert(0, "./gitlab_management")
sys.path.insert(0, "./")
from base import GitlabManagement
import unittest, xmlrunner
from unittest.mock import patch
import enum
import gitlab
import requests_mock
import gitlab_management
import yaml
class GitlabManagement_Unit(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.ConfigFileContents = """Group:
Labels:
-
Group: "ExampleGroup1"
Name: "Stage::Planning"
Description: "example description for the label"
Color: "#FF0000"
-
Group:
- "ExampleGroup1"
- "ExampleGroup2"
Name: "Stage::RequireFeedback"
Description: "example description for the label"
Color: "#FF0000"
"""
cls.ConfigFileContents_InvalidYML = """Group:
Labels:
-
Group "This Group label is missing the ':' so it's invalid"
Name: "Stage::Planning"
Description: "example description for the label"
Color: "#FF0000"
-
Group:
- "ExampleGroup1"
- "ExampleGroup2"
Name: "Stage::RequireFeedback"
Description: "example description for the label"
Color: "#FF0000"
"""
cls.Gitlab_Instance_URL = 'http://mock_gitlab_url'
cls.Gitlab_Instance_TOKEN = 'Gitlab token'
with open('config.yml', 'w') as ConfigFile:
ConfigFile.write(cls.ConfigFileContents)
ConfigFile.close()
cls.ClassToTest = GitlabManagement(cls.Gitlab_Instance_URL, cls.Gitlab_Instance_TOKEN, False)
def test___init__file(self):
# Check __init__.py to make sure all of the attributes are available
self.assertTrue('__title__' in gitlab_management.__dict__, msg="__init__.py missing attribute __titl__")
self.assertTrue('__version__' in gitlab_management.__dict__, msg="__init__.py missing attribute __version__")
self.assertTrue('__doc__' in gitlab_management.__dict__, msg="__init__.py missing attribute __doc__")
self.assertTrue('__author__' in gitlab_management.__dict__, msg="__init__.py missing attribute __author__")
self.assertTrue('__email__' in gitlab_management.__dict__, msg="__init__.py missing attribute __email__")
self.assertTrue('__license__' in gitlab_management.__dict__, msg="__init__.py missing attribute __license__")
self.assertTrue('__copyright__' in gitlab_management.__dict__, msg="__init__.py missing attribute __copyright__")
self.assertTrue('__source__' in gitlab_management.__dict__, msg="__init__.py missing attribute __source__")
def test_Config(self):
#ToDo fix this variable test, class should not be init before testing, so that it can be confirmed as existing before creating
self.assertTrue('_Config' in self.ClassToTest.__dict__, msg="_Config Variable missing")
#Within init, Config should be called, which populates `Config` variable, so this value should not be none
self.assertFalse(self.ClassToTest.Config is None, msg="Class has been initialized, value should not be `None`")
# Class has been init, config.yml loaded into `Config` variable.
self.assertTrue(type(self.ClassToTest.Config) is dict, msg="Config is collected during class init, `Config` should be a `dict` of the config.yml file contents")
# should not be able to change value
with self.assertRaises(AttributeError, msg="You should not be able to set this value"):
self.ClassToTest.Config = "IWontSet"
#test the property config.getter.Valid yaml
self.ClassToTest._Config = None
self.assertTrue(self.ClassToTest._Config == None, msg="precursor test setting '_Config' to None for Config.getter test")
self.assertTrue(self.ClassToTest.Config == yaml.safe_load(self.ConfigFileContents), msg="yaml file didn't load")
#invalid yaml
with open('config.yml', 'w') as ConfigFile:
ConfigFile.write(self.ConfigFileContents_InvalidYML)
ConfigFile.close()
#test the property config.getter. Invalid yaml
self.ClassToTest._Config = None
self.assertTrue(self.ClassToTest._Config == None, msg="precursor test setting '_Config' to None for Config.getter test")
with self.assertRaises(RuntimeError, msg="`RuntimeError` exception should have been thrown as the yaml was invalid"):
self.ClassToTest.Config
#Return yaml file t be valid
with open('config.yml', 'w') as ConfigFile:
ConfigFile.write(self.ConfigFileContents)
ConfigFile.close()
def test_CreateGroupLabel(self):
#self.skipTest('implement')
# not a group
self.assertEqual(self.ClassToTest.CreateGroupLabel(None, Name='boo', Description='boo', Color='000000'), False, msg='Invalid group returns false')
#is a group
with requests_mock.Mocker() as m:
with open('test/data/HTTPRequest_api_v4_groups_min_access_level.json', 'r') as Group_API_Call:
m.get(self.Gitlab_Instance_URL + '/api/v4/groups?min_access_level=40', text=Group_API_Call.read(), status_code=200)
GitLabGroup = self.ClassToTest.GetGroupByName('Documents')
with open('test/data/HTTPRequest_api_v4_groups_1_labels.json', 'r') as Group_API_Call:
m.get(self.Gitlab_Instance_URL + '/api/v4/groups/1/labels', text=Group_API_Call.read(), status_code=200)
with unittest.mock.patch('gitlab.v4.objects.GroupLabelManager.create', unittest.mock.Mock()) as Mock_resp:
self.assertEqual(self.ClassToTest.CreateGroupLabel(GitLabGroup, Name='feature', Description='boo', Color='000000'), True)
#Attempt to create a label that exists
self.assertEqual(self.ClassToTest.CreateGroupLabel(GitLabGroup, Name='Bug', Description='Label exists', Color='000000'), True)
def test_GetConfig(self):
try:
with open('config.yml', 'w') as ConfigFile:
ConfigFile.write(self.ConfigFileContents_InvalidYML)
ConfigFile.close()
self.assertFalse(self.ClassToTest.GetConfig(), msg="invalid yml files cause exception 'yaml.YAMLError' which needs to be handled by the method")
# make a valid Yaml file and test again
with open('config.yml', 'w') as ConfigFile:
ConfigFile.write(self.ConfigFileContents)
ConfigFile.close()
self.assertTrue(self.ClassToTest.GetConfig(), msg="valid yml files are loaded with the method returning true.")
#confirm what is in the file is loaded to the '_config' variable
self.assertTrue(self.ClassToTest._Config == yaml.safe_load(self.ConfigFileContents))
except yaml.YAMLError:
self.assertFalse(True, msg="Exception `yaml.YAMLError` needs to be caught by the method")
except Exception:
self.assertFalse(True, msg="Uncaught exceptions must be caught in the method")
def test_GetGroupByName(self):
#self.ClassToTest.GitlabObjectCache['Groups'] = None
#Group:gitlab.v4.objects.Group =
with requests_mock.Mocker() as m:
with open('test/data/HTTPRequest_api_v4_groups_min_access_level.json', 'r') as Group_API_Call:
m.get(self.Gitlab_Instance_URL + '/api/v4/groups?min_access_level=40', text=Group_API_Call.read(), status_code=200)
self.assertEqual(self.ClassToTest.GetGroupByName('Documents').name, 'Documents', msg='Unable to fetch group "Documents" by name')
self.assertEqual(self.ClassToTest.GetGroupByName('A Random Name to fail'), None, msg="group doesn't exist, this test should = 'None'")
#self.ClassToTest.GitlabObjectCache['Groups'] = None
def test_GetLabelByName(self):
try:
with requests_mock.Mocker() as m:
with open('test/data/HTTPRequest_api_v4_groups_1_labels.json', 'r') as Group_API_Call:
m.get(self.Gitlab_Instance_URL + '/api/v4/groups/1/labels', text=Group_API_Call.read(), status_code=200)
#GroupName = self.ClassToTest.GitlabObjectCache['Groups']['Projects']
self.assertEqual(self.ClassToTest.GetLabelByName(self.ClassToTest.GetGroupByName('Documents'), 'Bug').name, 'Bug', msg="Searching for an existing label failed when it should not have")
self.assertEqual(self.ClassToTest.GetLabelByName(self.ClassToTest.GetGroupByName('Documents'), 'Non existant label'), None, msg="Searching for a label that doesn't exist should return 'None'")
except Exception:
self.assertFalse(True, msg="An uncaught exception occured when the method should have caught it")
def test_GitlabLoginAuthenticate(self):
try:
with unittest.mock.patch('gitlab.Gitlab.auth', unittest.mock.Mock()):
self.assertTrue(self.ClassToTest.GitlabLoginAuthenticate(self.Gitlab_Instance_URL, self.Gitlab_Instance_TOKEN))
with requests_mock.Mocker() as m:
m.get(self.Gitlab_Instance_URL + '/api/v4/user', text='mock test, failed to auth', status_code=401)
self.assertFalse(self.ClassToTest.GitlabLoginAuthenticate(self.Gitlab_Instance_URL, self.Gitlab_Instance_TOKEN))
except gitlab.GitlabAuthenticationError:
self.assertFalse(True, msg="Exception 'gitlab.GitlabAuthenticationError' needs to be caught by the method")
except Exception:
self.assertFalse(True, msg="Uncaught exceptions must be caught in the method")
def test_GitlabSession(self):
self.assertTrue('_GitlabSession' in self.ClassToTest.__dict__, msg="_GitlabSession Variable missing")
#self.skipTest("functional")
ClassNotInit = GitlabManagement
self.assertTrue(type(ClassNotInit.GitlabSession) is property, msg="Class not initalized, should be a property")
self.assertTrue(type(self.ClassToTest.GitlabSession) == gitlab.Gitlab, msg="property must be type 'gitlab.Gitlab'")
with self.assertRaises(TypeError, msg='Should not be able to set to different type'):
boo:str = 'NotMe'
self.ClassToTest.GitlabSession = boo
def test_DesiredOutputLevel(self):
self.assertTrue('_DesiredOutputLevel' in self.ClassToTest.__dict__, msg="_DesiredOutputLevel Variable missing")
# Must be an int, however should be a GitlabManagement.OutputSeverity, can be none
self.assertTrue(type(self.ClassToTest.DesiredOutputLevel) is self.ClassToTest.OutputSeverity, msg="Must be of type `self.ClassToTest.OutputSeverity`")
#Should not be able to change type
with self.assertRaises(TypeError, msg="Type Error must be thrown if setting the type doens't equal `self.ClassToTest.OutputSeverity`"):
self.ClassToTest.DesiredOutputLevel = str('NoString')
with self.assertRaises(TypeError, msg="Type Error must be thrown if setting the type doens't equal `self.ClassToTest.OutputSeverity`"):
self.ClassToTest.DesiredOutputLevel = int(1)
self.assertFalse(self.ClassToTest.GitlabObjectCache is type(None), msg="during class init, a default value needs to be assigned")
def test_GitlabObjectCache(self):
self.assertTrue('_GitlabObjectCache' in self.ClassToTest.__dict__, msg="_GitlabObjectCache Variable missing")
# During init, `dict` is created with the required keys
self.assertNotIsInstance(self.ClassToTest.GitlabObjectCache, type(None), msg="Item should not be initialized with NoneType")
# Must be of type dict
self.assertTrue(type(self.ClassToTest.GitlabObjectCache) is dict, msg="Property must be of type `dict`")
# Must be dict, with Groups key
self.assertTrue('Groups' in self.ClassToTest.GitlabObjectCache, msg="Group Key Must exist in `dict`")
# ToDo: the line above test failed as now the getlabelby name makes a mock test. change this test to be a global check to see if the variable exists before the class has been init.
#Should not be able to change type
with self.assertRaises(AttributeError, msg="Attribute error should be thrown as no setter is required nor should exist"):
ListObject:list = []
self.ClassToTest.GitlabObjectCache = ListObject
def test_OutputSeverity_Class(self):
# Must be Enum
self.assertTrue(type(self.ClassToTest.OutputSeverity) is enum.EnumMeta, msg="Class must be an `enum`")
# All Class Properties must be int
self.assertTrue(type(self.ClassToTest.OutputSeverity.Emergency.value) is int, msg="Value must be an int")
self.assertTrue(type(self.ClassToTest.OutputSeverity.Alert.value) is int, msg="Value must be an int")
self.assertTrue(type(self.ClassToTest.OutputSeverity.Critical.value) is int, msg="Value must be an int")
self.assertTrue(type(self.ClassToTest.OutputSeverity.Error.value) is int, msg="Value must be an int")
self.assertTrue(type(self.ClassToTest.OutputSeverity.Warning.value) is int, msg="Value must be an int")
self.assertTrue(type(self.ClassToTest.OutputSeverity.Notice.value) is int, msg="Value must be an int")
self.assertTrue(type(self.ClassToTest.OutputSeverity.Informational.value) is int, msg="Value must be an int")
self.assertTrue(type(self.ClassToTest.OutputSeverity.Debug.value) is int, msg="Value must be an int")
def test_ProcessConfigLabels(self):
#self.skipTest('implement test')
with requests_mock.Mocker() as m:
with open('test/data/HTTPRequest_api_v4_groups_min_access_level.json', 'r') as Group_API_Call:
m.get(self.Gitlab_Instance_URL + '/api/v4/groups?min_access_level=40', text=Group_API_Call.read(), status_code=200)
with unittest.mock.patch('gitlab_management.base.GitlabManagement.CreateGroupLabel', unittest.mock.Mock()):
self.assertEqual(self.ClassToTest.ProcessConfigLabels(self.ClassToTest.Config['Group']['Labels']), True, msg="Process label should return true")
@classmethod
def tearDownClass(cls):
pass
if __name__ == '__main__':
#unittest.main()
with open('Unit.JUnit.xml', 'wb') as output:
unittest.main(
testRunner=xmlrunner.XMLTestRunner(output=output),
failfast=False, buffer=False, catchbreak=False)