diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..3977ef9 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,37 @@ +# This workflow will install Python dependencies, run tests and lint with a variety of Python versions +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python + +name: Python package + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + +jobs: + test: + + runs-on: ubuntu-20.04 + strategy: + fail-fast: false + matrix: + python-version: ["3.6", "3.7", "3.8", "3.9", "3.10", "3.11", "3.12"] + + steps: + - uses: actions/checkout@v4 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + python -m pip install '.[test]' + python -m pip install . + - name: Clear existing docker image cache + shell: bash + run: docker image prune -af + - name: Test with pytest + run: | + python -m unittest discover -v -s test -p "test*.py" diff --git a/.gitignore b/.gitignore index fed1142..f76e985 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ *.pyc build/ *.egg-info/ +.vscode \ No newline at end of file diff --git a/.project b/.project deleted file mode 100644 index 4d3d9e8..0000000 --- a/.project +++ /dev/null @@ -1,17 +0,0 @@ - - - python.channelfinder.api - - - - - - org.python.pydev.PyDevBuilder - - - - - - org.python.pydev.pythonNature - - diff --git a/.pydevproject b/.pydevproject deleted file mode 100644 index dc3c8ef..0000000 --- a/.pydevproject +++ /dev/null @@ -1,11 +0,0 @@ - - - - -Default -python 2.6 - -/python.channelfinder.api/example -/python.channelfinder.api - - diff --git a/MANIFEST b/MANIFEST deleted file mode 100644 index b2c34c4..0000000 --- a/MANIFEST +++ /dev/null @@ -1,9 +0,0 @@ -# file GENERATED by distutils, do NOT edit -cf-monitor-test -cf-update-ioc -setup.py -channelfinder\CFDataTypes.py -channelfinder\ChannelFinderClient.py -channelfinder\Cleanup.py -channelfinder\__init__.py -channelfinder\_conf.py diff --git a/README.md b/README.md index 8057572..7b76c3e 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # pyCFClient -A python client library +A python client library for ChannelFinder. -### configuration for pyCFClient +## Configuration The python channelfinder client library can be configured by setting up a `channelfinderapi.conf` file in the following locations @@ -11,6 +11,7 @@ The python channelfinder client library can be configured by setting up a `chann `channelfinderapi.conf` The example preferences: + ``` cat ~/channelfinderapi.conf [DEFAULT] @@ -19,3 +20,22 @@ username=MyUserName password=MyPassword ``` +## Development + +To install with dependancies for testing. + +```bash +python -m pip install --upgrade pip +python -m pip install '.[test]' +python -m pip install . +``` + +### Testing + +Some of the tests use docker to run a test ChannelFinderService, so a working docker installation needs to available for tests to be successful. + +To run all tests: + +```bash +python -m unittest discover -v -s test -p "test*.py" +``` \ No newline at end of file diff --git a/cf-update-ioc b/cf-update-ioc deleted file mode 100644 index 6221f72..0000000 --- a/cf-update-ioc +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/python -from channelfinder.cfUpdate.CFUpdateIOC import main -main() \ No newline at end of file diff --git a/cf-monitor-test b/channelfinder/cfMonitorTest.py similarity index 96% rename from cf-monitor-test rename to channelfinder/cfMonitorTest.py index 7326e7a..81a5366 100644 --- a/cf-monitor-test +++ b/channelfinder/cfMonitorTest.py @@ -1,131 +1,131 @@ -#!/usr/bin/python -''' -Created on Mar 19, 2012 - -A python script to ensure that the cf-update-ioc is working correctly. -The script requires the setup of two files -e.g. ->cat nagios01.host01.dbl ->test:cf-update-daemon{test} - ->cat nagios02.host02.dbl ->test:cf-update-daemon{test} - -The script will touch each file, with a short delay and will check that -Channelfinder has been appropriately updated. - -python cf-monitor-test /complete/path/to/daemon/dir -i initialFile -f finalFile - -@author: shroffk -''' -import sys -import os -import re -from optparse import OptionParser -from time import sleep - -from channelfinder import ChannelFinderClient - -SEVR = {0:'OK ', - 1:'Minor ', - 2:'Major '} - - -def main(): - requiredOpts = ['initial-file', 'final-file'] - usage = "usage: %prog -i initial-file -f final-file directory " - parser = OptionParser(usage=usage) - parser.add_option('-i', '--initial-file', \ - action='store', type='string', dest='initialFile', \ - help='the initial-file') - parser.add_option('-f', '--final-file', \ - action='store', type='string', dest='finalFile', \ - help='the --final-file') - opts, args = parser.parse_args() - if args == None or len(args) == 0 : - parser.error('Please specify a directory') - if not opts.initialFile: - parser.error('Please specify a initial test files') - if not opts.finalFile: - parser.error('Please specify a final test files') - mainRun(opts, args) - - -def mainRun(opts, args): - for directory in args: - initialFile = os.path.normpath(directory + '/' + opts.initialFile) - iHostName, iIocName = getArgsFromFilename(initialFile) - finalFile = os.path.normpath(directory + '/' + opts.finalFile) - fHostName, fIocName = getArgsFromFilename(finalFile) - if getPVNames(initialFile) != getPVNames(finalFile): - sys.exit(1) - pvNames = getPVNames(initialFile) - if len(pvNames) == 0: - sys.exit(1) - ''' - Touch the initial file and check channelfinder - ''' - touch(initialFile) - sleep(2) - check(pvNames, iHostName, iIocName) - ''' - Touch the final file and check channelfinder - ''' - touch(finalFile) - sleep(2) - check(pvNames, fHostName, fIocName) - sys.exit - - -def check(pvNames, hostName, iocName): - try: - client = ChannelFinderClient() - except: - raise RuntimeError('Unable to create a valid webResourceClient') - channels = client.find(property=[('hostName', hostName), ('iocName', iocName)]) - if channels and len(pvNames) == len(channels): - for channel in channels: - if channel.Name not in pvNames: - sys.exit(2) - else: - sys.exit(2) - - -def touch(fname, times=None): - with open(fname, 'a'): - os.utime(fname, times) - - -def getArgsFromFilename(completeFilePath): - fileName = os.path.split(os.path.normpath(completeFilePath))[1] - pattern4Hostname = '(\S+?)\.\S+' - match = re.search(pattern4Hostname, fileName) - if match: - hostName = match.group(1) - else: - hostName = None - pattern4Iocname = '\S+?\.(\S+?)\.\S+' - match = re.search(pattern4Iocname, fileName) - if match: - iocName = match.group(1) - else: - iocName = None - return hostName, iocName - -def getPVNames(completeFilePath, pattern=None): - try: - f = open(completeFilePath) - pvNames = f.read().splitlines() - pvNames = map(lambda x: x.strip(), pvNames) - pvNames = filter(lambda x: len(x) > 0, pvNames) - if pattern: - pvNames = [ re.match(pattern, pvName).group() for pvName in pvNames if re.match(pattern, pvName) ] - return pvNames - except IOError: - return None - finally: - f.close() - -if __name__ == '__main__': - main() - pass +#!/usr/bin/python +''' +Created on Mar 19, 2012 + +A python script to ensure that the cf-update-ioc is working correctly. +The script requires the setup of two files +e.g. +>cat nagios01.host01.dbl +>test:cf-update-daemon{test} + +>cat nagios02.host02.dbl +>test:cf-update-daemon{test} + +The script will touch each file, with a short delay and will check that +Channelfinder has been appropriately updated. + +python cf-monitor-test /complete/path/to/daemon/dir -i initialFile -f finalFile + +@author: shroffk +''' +import sys +import os +import re +from optparse import OptionParser +from time import sleep + +from channelfinder import ChannelFinderClient + +SEVR = {0:'OK ', + 1:'Minor ', + 2:'Major '} + + +def main(): + requiredOpts = ['initial-file', 'final-file'] + usage = "usage: %prog -i initial-file -f final-file directory " + parser = OptionParser(usage=usage) + parser.add_option('-i', '--initial-file', \ + action='store', type='string', dest='initialFile', \ + help='the initial-file') + parser.add_option('-f', '--final-file', \ + action='store', type='string', dest='finalFile', \ + help='the --final-file') + opts, args = parser.parse_args() + if args == None or len(args) == 0 : + parser.error('Please specify a directory') + if not opts.initialFile: + parser.error('Please specify a initial test files') + if not opts.finalFile: + parser.error('Please specify a final test files') + mainRun(opts, args) + + +def mainRun(opts, args): + for directory in args: + initialFile = os.path.normpath(directory + '/' + opts.initialFile) + iHostName, iIocName = getArgsFromFilename(initialFile) + finalFile = os.path.normpath(directory + '/' + opts.finalFile) + fHostName, fIocName = getArgsFromFilename(finalFile) + if getPVNames(initialFile) != getPVNames(finalFile): + sys.exit(1) + pvNames = getPVNames(initialFile) + if len(pvNames) == 0: + sys.exit(1) + ''' + Touch the initial file and check channelfinder + ''' + touch(initialFile) + sleep(2) + check(pvNames, iHostName, iIocName) + ''' + Touch the final file and check channelfinder + ''' + touch(finalFile) + sleep(2) + check(pvNames, fHostName, fIocName) + sys.exit + + +def check(pvNames, hostName, iocName): + try: + client = ChannelFinderClient() + except: + raise RuntimeError('Unable to create a valid webResourceClient') + channels = client.find(property=[('hostName', hostName), ('iocName', iocName)]) + if channels and len(pvNames) == len(channels): + for channel in channels: + if channel.Name not in pvNames: + sys.exit(2) + else: + sys.exit(2) + + +def touch(fname, times=None): + with open(fname, 'a'): + os.utime(fname, times) + + +def getArgsFromFilename(completeFilePath): + fileName = os.path.split(os.path.normpath(completeFilePath))[1] + pattern4Hostname = '(\S+?)\.\S+' + match = re.search(pattern4Hostname, fileName) + if match: + hostName = match.group(1) + else: + hostName = None + pattern4Iocname = '\S+?\.(\S+?)\.\S+' + match = re.search(pattern4Iocname, fileName) + if match: + iocName = match.group(1) + else: + iocName = None + return hostName, iocName + +def getPVNames(completeFilePath, pattern=None): + try: + f = open(completeFilePath) + pvNames = f.read().splitlines() + pvNames = map(lambda x: x.strip(), pvNames) + pvNames = filter(lambda x: len(x) > 0, pvNames) + if pattern: + pvNames = [ re.match(pattern, pvName).group() for pvName in pvNames if re.match(pattern, pvName) ] + return pvNames + except IOError: + return None + finally: + f.close() + +if __name__ == '__main__': + main() + pass diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..6182786 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,33 @@ +[build-system] +requires = ["flit_core >=3.2,<4"] +build-backend = "flit_core.buildapi" + +[project] +name = "channelfinder" +version = "3.0.0" +authors = [ + { name="Kunal Shroff", email="shroffk@bnl.gov" }, +] +description = "Python ChannelFinder Client Lib" +readme = "README.md" +requires-python = ">=3.6" +classifiers = [ + "Programming Language :: Python :: 3", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", +] +dependencies = [ + "requests>=2.15.0", + "simplejson>=3.10.0", + "urllib3>=1.22" + ] +[project.optional-dependencies] +test = ["pytest", "testcontainers>=3.7.0,<4"] + +[project.urls] +Homepage = "https://github.com/ChannelFinderService/pyCFClient" +Issues = "https://github.com/ChannelFinderService/pyCFClient/issues" + +[project.scripts] +cf-update-ioc = "channelfinder.cfUpdate.CFUpdateIOC:main" +cf-monitor-test = "channelfinder.cfMonitorTest:main" diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 46bc311..0000000 --- a/requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -requests>=2.15.0 -simplejson>=3.10.0 -urllib3>=1.22 \ No newline at end of file diff --git a/setup.py b/setup.py deleted file mode 100644 index 655aa94..0000000 --- a/setup.py +++ /dev/null @@ -1,38 +0,0 @@ -''' -Created on Apr 4, 2011 - -@author: shroffk -''' - -from setuptools import setup - -from os import path - -cur_dir = path.abspath(path.dirname(__file__)) - -with open(path.join(cur_dir, 'requirements.txt')) as f: - requirements = f.read().split() - -extras_require = { - 'PySide': ['PySide'], - 'pyepics': ['pyepics'], - 'perf': ['psutil'], - 'testing-ioc': ['pcaspy'], - 'test': ['codecov', 'pytest', 'pytest-cov', 'coverage', 'coveralls', 'pcaspy'] -} - -setup( - name='channelfinder', - version='3.0.0', - description='Python ChannelFinder Client Lib', - author='Kunal Shroff', - author_email='shroffk@bnl.gov', - url='http://channelfinder.sourceforge.net/channelfinderpy', - scripts=['cf-update-ioc', 'cf-monitor-test'], - packages=['channelfinder', 'channelfinder/util', 'channelfinder/cfUpdate', 'test'], - long_description="""\ - Python ChannelFinder Client Lib - """, - license='GPL', - install_requires=requirements, -) diff --git a/test/_testConf.py b/test/_testConf.py index acb28b0..ffd3eda 100644 --- a/test/_testConf.py +++ b/test/_testConf.py @@ -12,6 +12,8 @@ password=MyPassword """ import os.path +import unittest +from testcontainers.compose import DockerCompose import sys if sys.version_info[0] < 3: @@ -21,22 +23,41 @@ # Python 3 code in this block from configparser import ConfigParser +def channelFinderDocker(): + return DockerCompose("test", compose_file_name="docker-compose.yml") + +class ChannelFinderClientTestCase(unittest.TestCase): + channelFinderCompose = None + @classmethod + def setUpClass(cls) -> None: + cls.channelFinderCompose = channelFinderDocker() + cls.channelFinderCompose.start() + cls.channelFinderCompose.wait_for(_testConf.get('DEFAULT', 'BaseURL') + "/ChannelFinder") + return super().setUpClass() + + @classmethod + def tearDownClass(cls) -> None: + if cls.channelFinderCompose is not None: + cls.channelFinderCompose.stop() + return super().tearDownClass() + def __loadConfig(): - dflt={'BaseURL':'https://barkeria-vm:8181/ChannelFinder', - 'username' : 'cf-update', - 'password' : '1234', - 'owner' : 'cf-update', - 'channelOwner' : 'cf-channels', - 'channelUsername' : 'channel', - 'channelPassword' : '1234', - 'propOwner' : 'cf-properties', - 'propUsername' : 'property', - 'propPassword' : '1234', - 'tagOwner' : 'cf-tags', - 'tagUsername' : 'tag', - 'tagPassword' : '1234' - } + + dflt = {'BaseURL':'http://localhost:8080/ChannelFinder', + 'username' : 'admin', + 'password' : 'adminPass', + 'owner' : 'cf-update', + 'channelOwner' : 'cf-channels', + 'channelUsername' : 'admin', + 'channelPassword' : 'adminPass', + 'propOwner' : 'cf-properties', + 'propUsername' : 'admin', + 'propPassword' : 'adminPass', + 'tagOwner' : 'cf-tags', + 'tagUsername' : 'admin', + 'tagPassword' : 'adminPass' + } cf=ConfigParser(defaults=dflt) cf.read([ '/etc/channelfinderapi.conf', @@ -45,4 +66,4 @@ def __loadConfig(): ]) return cf -_testConf=__loadConfig() \ No newline at end of file +_testConf=__loadConfig() diff --git a/test/docker-compose.yml b/test/docker-compose.yml new file mode 100644 index 0000000..dc9b22d --- /dev/null +++ b/test/docker-compose.yml @@ -0,0 +1,67 @@ +# ------------------------------------------------------------------------------ +# Copyright (C) 2021 European Spallation Source ERIC. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# ------------------------------------------------------------------------------ + +version: "3.7" +services: + channelfinder: + image: ghcr.io/channelfinder/channelfinderservice:master + hostname: channelfinder + networks: + - channelfinder-net + ports: + - 8080:8080 + - 8443:8443 + depends_on: + elasticsearch: + condition: service_healthy + environment: + elasticsearch.network.host: elasticsearch-cf + elasticsearch.http.port: 9200 + ldap.enabled: "false" + embedded_ldap.enabled: "false" + demo_auth.enabled: "true" + skipITCoverage: "true" + EPICS_PVAS_INTF_ADDR_LIST: "0.0.0.0" + aa.enabled: "false" + restart: on-failure + healthcheck: + test: curl -s -f http://channelfinder:8080/ChannelFinder + interval: 30s + timeout: 60s + retries: 5 + + elasticsearch: + image: docker.elastic.co/elasticsearch/elasticsearch:8.11.4 + hostname: elasticsearch-cf + networks: + - channelfinder-net + environment: + cluster.name: channelfinder + bootstrap.memory_lock: "true" + discovery.type: single-node + ES_JAVA_OPTS: "-Xms512m -Xmx512m" + xpack.security.enabled: "false" + healthcheck: + test: curl -f http://elasticsearch-cf:9200/_cluster/health + interval: 30s + timeout: 60s + retries: 5 + +networks: + channelfinder-net: + driver: bridge diff --git a/test/testCFPpropertyManager.py b/test/testCFPpropertyManager.py index a770e03..66607d1 100644 --- a/test/testCFPpropertyManager.py +++ b/test/testCFPpropertyManager.py @@ -5,13 +5,13 @@ import re import os -from _testConf import _testConf +from _testConf import _testConf, ChannelFinderClientTestCase import urllib3 urllib3.disable_warnings() -class CFPropertyManagerTest(unittest.TestCase): +class CFPropertyManagerTest(ChannelFinderClientTestCase): cfglines = [] diff --git a/test/testCFUpdateIOC.py b/test/testCFUpdateIOC.py index 13b8ce2..a14fa38 100644 --- a/test/testCFUpdateIOC.py +++ b/test/testCFUpdateIOC.py @@ -14,13 +14,13 @@ from tempfile import NamedTemporaryFile from copy import copy -from _testConf import _testConf +from _testConf import _testConf, ChannelFinderClientTestCase import urllib3 urllib3.disable_warnings() -class Test(unittest.TestCase): +class UpdateIOCTest(ChannelFinderClientTestCase): def setUp(self): if _testConf.has_option('DEFAULT', 'BaseURL'): self.baseURL = _testConf.get('DEFAULT', 'BaseURL') @@ -167,6 +167,9 @@ def testPreservingOfAttributes(self): client.set(channel={u'name':u'cf-update-pv1', u'owner':u'cf-update', u'properties':[unaffectedProperty], u'tags':[unaffectedTag]}) client.set(channel={u'name':u'cf-update-pv2', u'owner':u'cf-update', u'properties':[unaffectedProperty], u'tags':[unaffectedTag]}) + unaffectedProperty['channels'] = [] + unaffectedTag['channels'] = [] + # Case1: hostName = 'initialHost' iocName = 'initialIoc' diff --git a/test/testChannelFinderClient.py b/test/testChannelFinderClient.py index a1a6680..eb7a3b0 100644 --- a/test/testChannelFinderClient.py +++ b/test/testChannelFinderClient.py @@ -11,14 +11,14 @@ from channelfinder import ChannelFinderClient from channelfinder.util import ChannelUtil -from _testConf import _testConf +from _testConf import _testConf, ChannelFinderClientTestCase import urllib3 urllib3.disable_warnings() -class ConnectionTest(unittest.TestCase): +class ConnectionTest(ChannelFinderClientTestCase): def testConnection(self): testUrl = getDefaultTestConfig("BaseURL") self.assertNotEqual(ChannelFinderClient(BaseURL=testUrl, @@ -34,7 +34,7 @@ def testConnection(self): # Test JSON Parsing # =============================================================================== """ -class JSONparserTest(unittest.TestCase): +class JSONparserTest(ChannelFinderClientTestCase): multiChannels = {u'channels': {u'channel': [{u'@owner': u'shroffk', u'@name': u'Test_first:a<000>:0:0', u'properties': {u'property': [{u'@owner': u'shroffk', u'@name': u'Test_PropA', u'@value': u'0'}, {u'@owner': u'shroffk', u'@name': u'Test_PropB', u'@value': u'19'}, {u'@owner': u'shroffk', u'@name': u'Test_PropC', u'@value': u'ALL'}]}, u'tags': {u'tag': [{u'@owner': u'shroffk', u'@name': u'Test_TagA'}, {u'@owner': u'shroffk', u'@name': u'Test_TagB'}]}}, {u'@owner': u'shroffk', u'@name': u'Test_first:a<000>:0:1', u'properties': {u'property': [{u'@owner': u'shroffk', u'@name': u'Test_PropA', u'@value': u'1'}, {u'@owner': u'shroffk', u'@name': u'Test_PropB', u'@value': u'22'}, {u'@owner': u'shroffk', u'@name': u'Test_PropC', u'@value': u'ALL'}]}, u'tags': {u'tag': [{u'@owner': u'shroffk', u'@name': u'Test_TagA'}, {u'@owner': u'shroffk', u'@name': u'Test_TagB'}]}}, {u'@owner': u'shroffk', u'@name': u'Test_first:a<000>:0:2', u'properties': {u'property': [{u'@owner': u'shroffk', u'@name': u'Test_PropA', u'@value': u'2'}, {u'@owner': u'shroffk', u'@name': u'Test_PropB', u'@value': u'38'}, {u'@owner': u'shroffk', u'@name': u'Test_PropC', u'@value': u'ALL'}]}, u'tags': {u'tag': [{u'@owner': u'shroffk', u'@name': u'Test_TagA'}, {u'@owner': u'shroffk', u'@name': u'Test_TagB'}]}}, {u'@owner': u'shroffk', u'@name': u'Test_first:a<000>:0:3', u'properties': {u'property': [{u'@owner': u'shroffk', u'@name': u'Test_PropA', u'@value': u'3'}, {u'@owner': u'shroffk', u'@name': u'Test_PropB', u'@value': u'65'}, {u'@owner': u'shroffk', u'@name': u'Test_PropC', u'@value': u'ALL'}]}, u'tags': {u'tag': [{u'@owner': u'shroffk', u'@name': u'Test_TagA'}, {u'@owner': u'shroffk', u'@name': u'Test_TagB'}]}}, {u'@owner': u'shroffk', u'@name': u'Test_first:a<000>:0:4', u'properties': {u'property': [{u'@owner': u'shroffk', u'@name': u'Test_PropA', u'@value': u'4'}, {u'@owner': u'shroffk', u'@name': u'Test_PropB', u'@value': u'78'}, {u'@owner': u'shroffk', u'@name': u'Test_PropC', u'@value': u'ALL'}]}, u'tags': {u'tag': [{u'@owner': u'shroffk', u'@name': u'Test_TagA'}, {u'@owner': u'shroffk', u'@name': u'Test_TagB'}]}}, {u'@owner': u'shroffk', u'@name': u'Test_first:a<000>:0:5', u'properties': {u'property': [{u'@owner': u'shroffk', u'@name': u'Test_PropA', u'@value': u'5'}, {u'@owner': u'shroffk', u'@name': u'Test_PropB', u'@value': u'79'}, {u'@owner': u'shroffk', u'@name': u'Test_PropC', u'@value': u'ALL'}]}, u'tags': {u'tag': [{u'@owner': u'shroffk', u'@name': u'Test_TagA'}, {u'@owner': u'shroffk', u'@name': u'Test_TagB'}]}}, {u'@owner': u'shroffk', u'@name': u'Test_first:a<000>:0:6', u'properties': {u'property': [{u'@owner': u'shroffk', u'@name': u'Test_PropA', u'@value': u'6'}, {u'@owner': u'shroffk', u'@name': u'Test_PropB', u'@value': u'90'}, {u'@owner': u'shroffk', u'@name': u'Test_PropC', u'@value': u'ALL'}]}, u'tags': {u'tag': [{u'@owner': u'shroffk', u'@name': u'Test_TagA'}, {u'@owner': u'shroffk', u'@name': u'Test_TagB'}]}}, {u'@owner': u'shroffk', u'@name': u'Test_first:a<000>:0:7', u'properties': {u'property': [{u'@owner': u'shroffk', u'@name': u'Test_PropA', u'@value': u'7'}, {u'@owner': u'shroffk', u'@name': u'Test_PropB', u'@value': u'5'}, {u'@owner': u'shroffk', u'@name': u'Test_PropC', u'@value': u'ALL'}]}, u'tags': {u'tag': [{u'@owner': u'shroffk', u'@name': u'Test_TagA'}, {u'@owner': u'shroffk', u'@name': u'Test_TagB'}]}}, {u'@owner': u'shroffk', u'@name': u'Test_first:a<000>:0:8', u'properties': {u'property': [{u'@owner': u'shroffk', u'@name': u'Test_PropA', u'@value': u'8'}, {u'@owner': u'shroffk', u'@name': u'Test_PropB', u'@value': u'64'}, {u'@owner': u'shroffk', u'@name': u'Test_PropC', u'@value': u'ALL'}]}, u'tags': {u'tag': [{u'@owner': u'shroffk', u'@name': u'Test_TagA'}, {u'@owner': u'shroffk', u'@name': u'Test_TagB'}]}}, {u'@owner': u'shroffk', u'@name': u'Test_first:a<000>:0:9', u'properties': {u'property': [{u'@owner': u'shroffk', u'@name': u'Test_PropA', u'@value': u'9'}, {u'@owner': u'shroffk', u'@name': u'Test_PropB', u'@value': u'85'}, {u'@owner': u'shroffk', u'@name': u'Test_PropC', u'@value': u'ALL'}]}, u'tags': {u'tag': [{u'@owner': u'shroffk', u'@name': u'Test_TagA'}, {u'@owner': u'shroffk', u'@name': u'Test_TagB'}]}}]}} singleChannels = {u'channels': {u'channel': {u'@owner': u'shroffk', u'@name': u'Test_first:a<000>:0:2', u'properties': {u'property': [{u'@owner': u'shroffk', u'@name': u'Test_PropA', u'@value': u'2'}, {u'@owner': u'shroffk', u'@name': u'Test_PropB', u'@value': u'38'}, {u'@owner': u'shroffk', u'@name': u'Test_PropC', u'@value': u'ALL'}]}, u'tags': {u'tag': [{u'@owner': u'shroffk', u'@name': u'Test_TagA'}, {u'@owner': u'shroffk', u'@name': u'Test_TagB'}]}}}} @@ -91,7 +91,7 @@ def testEncodeChannels(self): # =============================================================================== # Test all the tag operations # =============================================================================== -class OperationTagTest(unittest.TestCase): +class OperationTagTest(ChannelFinderClientTestCase): def setUp(self): """Default Owners""" self.channelOwner = _testConf.get('DEFAULT', 'channelOwner') @@ -279,7 +279,7 @@ def testGetAllTags(self): # Test all the property operations # =============================================================================== -class OperationPropertyTest(unittest.TestCase): +class OperationPropertyTest(ChannelFinderClientTestCase): def setUp(self): """Default Owners""" self.channelOwner = _testConf.get('DEFAULT', 'channelOwner') @@ -431,7 +431,7 @@ def testGetAllPropperties(self): # =============================================================================== # # =============================================================================== -class OperationChannelTest(unittest.TestCase): +class OperationChannelTest(ChannelFinderClientTestCase): def setUp(self): """Default Owners""" self.channelOwner = _testConf.get('DEFAULT', 'channelOwner') @@ -653,7 +653,7 @@ def testUpdateChannel(self): # Update Opertation Tests # =============================================================================== -class UpdateOperationTest(unittest.TestCase): +class UpdateOperationTest(ChannelFinderClientTestCase): def setUp(self): """Default set of Owners""" self.channelOwner = _testConf.get('DEFAULT', 'channelOwner') @@ -673,6 +673,8 @@ def setUp(self): """ Test Properties and Tags """ self.orgTag = {u'name': u'originalTag', u'owner': self.tagOwner} self.orgProp = {u'name': u'originalProp', u'owner': self.propOwner, u'value': u'originalValue'} + self.orgTagResponse = {u'name': u'originalTag', u'owner': self.tagOwner, u'channels': []} + self.orgPropResponse = {u'name': u'originalProp', u'owner': self.propOwner, u'value': u'originalValue', u'channels': []} self.clientTag.set(tag=self.orgTag) self.clientProp.set(property=self.orgProp) @@ -683,9 +685,8 @@ def setUp(self): u'tags': [self.orgTag]}) ch = self.client.find(name=u'originalChannelName') self.assertTrue(len(ch) == 1 and - self.orgProp in ch[0][u'properties'] and - self.orgTag in ch[0][u'tags']) - pass + self.orgPropResponse in ch[0][u'properties'] and + self.orgTagResponse in ch[0][u'tags']) def UpdateTagName(self): newTagName = 'updatedTag' @@ -696,13 +697,13 @@ def UpdateTagName(self): self.client.findTag(newTagName) is not None) # check that renaming the Tag does not remove it from any channel channelTags = self.client.find(name=u'originalChannelName')[0][u'tags'] - self.assertTrue(self.orgTag not in channelTags and - {u'name': newTagName, u'owner': self.tagOwner} in channelTags) + self.assertTrue(self.orgTagResponse not in channelTags and + {u'name': newTagName, u'owner': self.tagOwner, u'channels': []} in channelTags) self.clientTag.update(tag=self.orgTag, originalTagName=newTagName) def testUpdateTagOwner(self): """Test implemented in testUpdateTag""" - pass + self.assertTrue(True) # removed test till bug in the sevice is fixed - channelfinder needs to check for the existance of oldname not name def UpdatePropName(self): @@ -719,7 +720,7 @@ def UpdatePropName(self): self.clientProp.update(property=self.orgProp, originalPropertyName=newPropName) def testUpdatePropOwner(self): - pass + self.assertTrue(True) def testUpdateChannelName(self): ch = self.client.find(name=u'originalChannelName')[0] @@ -742,7 +743,6 @@ def UpdateChannelOwner(self): self.clientCh.update(originalChannelName=u'originalChannelName', channel=newChannel) self.assertTrue(self.client.find(name=u'originalChannelName')[0][u'owner'] == self.tagOwner) - pass def testUpdateChannel(self): """ @@ -758,6 +758,9 @@ def testUpdateChannel(self): updatedProp = {u'name': u'originalProp', u'owner': self.propOwner, u'value': u'updatedValue'} newTag = {u'name': u'updatedTag', u'owner': self.tagOwner} newProp = {u'name': u'newProp', u'owner': self.propOwner, u'value': u'newValue'} + updatedPropResponse = {u'name': u'originalProp', u'owner': self.propOwner, u'value': u'updatedValue', u'channels': []} + newTagResponse = {u'name': u'updatedTag', u'owner': self.tagOwner, u'channels': []} + newPropResponse = {u'name': u'newProp', u'owner': self.propOwner, u'value': u'newValue', u'channels': []} try: self.clientTag.set(tag=newTag) self.clientProp.set(property=newProp) @@ -767,12 +770,12 @@ def testUpdateChannel(self): self.clientCh.update(originalChannelName=u'originalChannelName', channel=newChannel) foundChannel = self.client.find(name=u'updatedChannelName')[0] - self.assertTrue(foundChannel[u'name'] == u'updatedChannelName' and - foundChannel[u'owner'] == self.channelOwner and - updatedProp in foundChannel[u'properties'] and - newProp in foundChannel[u'properties'] and - newTag in foundChannel[u'tags'] and - self.orgTag in foundChannel[u'tags']) + self.assertTrue(foundChannel[u'name'] == u'updatedChannelName' ) + self.assertTrue(foundChannel[u'owner'] == self.channelOwner ) + self.assertTrue(updatedPropResponse in foundChannel[u'properties'] ) + self.assertTrue(newPropResponse in foundChannel[u'properties'] ) + self.assertTrue(newTagResponse in foundChannel[u'tags'] ) + self.assertTrue(self.orgTagResponse in foundChannel[u'tags']) finally: # reset @@ -791,20 +794,23 @@ def testUpdateChannel2(self): """ ch = self.client.find(name=u'originalChannelName') self.assertTrue(len(ch) == 1 and - self.orgProp in ch[0][u'properties'] and - self.orgTag in ch[0][u'tags']) - updated_prop = {u'name': u'originalProp', + self.orgPropResponse in ch[0][u'properties'] and + self.orgTagResponse in ch[0][u'tags']) + updatedProp = {u'name': u'originalProp', u'owner': self.propOwner, u'value': u'newPropValue'} self.clientCh.update(channel={u'name': u'originalChannelName', u'owner': u'newOwner', - u'properties': [updated_prop], + u'properties': [updatedProp], u'tags': []}) ch = self.client.find(name=u'originalChannelName') - self.assertTrue(len(ch) == 1 and - updated_prop in ch[0][u'properties'] and - self.orgTag in ch[0][u'tags'] and - ch[0][u'owner'] == u'newOwner') + updatedPropResponse = {u'name': u'originalProp', + u'owner': self.propOwner, + u'value': u'newPropValue', u'channels':[]} + self.assertTrue(len(ch) == 1) + self.assertTrue(updatedPropResponse in ch[0][u'properties']) + self.assertTrue(self.orgTagResponse in ch[0][u'tags']) + self.assertTrue(ch[0][u'owner'] == u'newOwner') def testUpdateProperty(self): """ @@ -812,6 +818,7 @@ def testUpdateProperty(self): Updates existing channels with new property owner, without altering original value. """ prop = self.client.findProperty(propertyname=u'originalProp') + prop[u'channels'] = [] self.assertDictEqual(prop, {u'owner': self.propOwner, u'channels': [], u'name': u'originalProp', @@ -822,6 +829,7 @@ def testUpdateProperty(self): self.clientProp.update(property=updatedProperty) """Check property owner""" prop = self.client.findProperty(propertyname=u'originalProp') + prop[u'channels'] = [] self.assertDictEqual(prop, {u'owner': u'newOwner', u'channels': [], u'name': u'originalProp', @@ -830,7 +838,7 @@ def testUpdateProperty(self): ch = self.client.find(name=u'originalChannelName') self.assertTrue({u'owner': u'newOwner', u'name': u'originalProp', - u'value': u'originalValue'} in ch[0][u'properties']) + u'value': u'originalValue', u'channels': []} in ch[0][u'properties']) def testUpdateTag(self): """ @@ -838,6 +846,7 @@ def testUpdateTag(self): Updates owner in all associated channels. """ tag = self.client.findTag(tagname=u'originalTag') + tag[u'channels'] = [] self.assertDictEqual(tag, {u'owner': self.tagOwner, u'channels': [], u'name': u'originalTag'}) updatedTag = dict(tag) @@ -845,17 +854,17 @@ def testUpdateTag(self): self.clientTag.update(tag=updatedTag) """Check tag owner""" tag = self.client.findTag(tagname=u'originalTag') + tag[u'channels'] = [] self.assertDictEqual(tag, {u'owner': u'newOwner', u'channels': [], u'name': u'originalTag'}) """Checks existing channel""" ch = self.client.find(name=u'originalChannelName') self.assertTrue({u'owner': u'newOwner', - u'name': u'originalTag'} in ch[0][u'tags']) + u'name': u'originalTag', u'channels':[]} in ch[0][u'tags']) def tearDown(self): self.clientCh.delete(channelName=u'originalChannelName') self.clientTag.delete(tagName=u'originalTag') self.clientProp.delete(propertyName=u'originalProp') - pass """ @@ -865,7 +874,7 @@ def tearDown(self): """ -class UpdateAppendTest(unittest.TestCase): +class UpdateAppendTest(ChannelFinderClientTestCase): def setUp(self): """Default Owners""" self.ChannelOwner = _testConf.get('DEFAULT', 'channelOwner') @@ -886,6 +895,11 @@ def setUp(self): self.Tag2 = {u'name': u'tag2', u'owner': self.tagOwner} self.Prop1 = {u'name': u'prop1', u'owner': self.propOwner, u'value': u'initialVal'} self.Prop2 = {u'name': u'prop2', u'owner': self.propOwner, u'value': u'initialVal'} + self.Prop1Response = self.Prop1.copy() + self.Prop1Response["channels"] = [] + self.Prop2Response = {u'name': u'prop2', u'owner': self.propOwner, u'value': u'initialVal'} + self.Prop2Response = self.Prop2.copy() + self.Prop2Response["channels"] = [] self.ch1 = {u'name': u'orgChannel1', u'owner': self.ChannelOwner, u'tags': [self.Tag1, self.Tag2]} self.ch2 = {u'name': u'orgChannel2', u'owner': self.ChannelOwner, u'tags': [self.Tag2]} self.ch3 = {u'name': u'orgChannel3', u'owner': self.ChannelOwner} @@ -927,20 +941,22 @@ def testUpdateAppendProperty2Channel(self): """ Test to update a channel with a property """ - self.assertTrue(len(self.client.find(name=self.ch3[u'name'])) == 1 and - self.client.find(name=self.ch3[u'name'])[0][u'properties'] == [], + self.assertEqual(len(self.client.find(name=self.ch3[u'name'])), 1) + self.assertEqual(self.client.find(name=self.ch3[u'name'])[0][u'properties'], [], 'the channel already has properties') self.clientProp.update(property=self.Prop1, channelName=self.ch3[u'name']) - self.assertTrue(len(self.client.find(name=self.ch3[u'name'])) == 1 and - self.Prop1 in self.client.find(name=self.ch3[u'name'])[0][u'properties'], + self.assertEqual(len(self.client.find(name=self.ch3[u'name'])), 1) + ch3 = self.client.find(name=self.ch3[u'name']) + self.assertTrue(self.Prop1Response in ch3[0][u'properties'], 'failed to update the channel with a new property') """Check that Value of the property is correctly added""" self.Prop2[u'value'] = 'val' + self.Prop2Response[u'value'] = 'val' self.clientProp.update(property=self.Prop2, channelName=self.ch3[u'name']) chs = self.client.find(name=self.ch3[u'name']) self.assertTrue(len(chs) == 1 and - self.Prop1 in chs[0][u'properties'] and - self.Prop2 in chs[0][u'properties'], + self.Prop1Response in chs[0][u'properties'] and + self.Prop2Response in chs[0][u'properties'], 'Failed to update the channel with a new property without disturbing the old one') self.client.set(channel=self.ch3) @@ -955,12 +971,13 @@ def testUpdateAppendProperty2Channels(self): self.client.find(name=self.ch3[u'name'])[0][u'properties'] == [], 'the channel already has properties') self.Prop1[u'value'] = 'testVal' + self.Prop1Response[u'value'] = 'testVal' self.clientProp.update(property=self.Prop1, channelNames=[self.ch2[u'name'], self.ch3[u'name']]) self.assertTrue(len(self.client.find(name=self.ch2[u'name'])) == 1 and - self.Prop1 in self.client.find(name=self.ch2[u'name'])[0][u'properties'], + self.Prop1Response in self.client.find(name=self.ch2[u'name'])[0][u'properties'], 'failed to update the channel with a new property') self.assertTrue(len(self.client.find(name=self.ch3[u'name'])) == 1 and - self.Prop1 in self.client.find(name=self.ch3[u'name'])[0][u'properties'], + self.Prop1Response in self.client.find(name=self.ch3[u'name'])[0][u'properties'], 'failed to update the channel with a new property') @unittest.skip("Skipping test for unimplemented functionality.") @@ -971,7 +988,7 @@ def testUpdateRemoveProperty2Channel(self): try: self.client.set(channel={u'name': u'testChannel', u'owner': self.ChannelOwner, u'properties': [self.Prop1]}) channel = self.client.find(name=u'testChannel') - self.assertTrue(len(channel) == 1 and self.Prop1 in channel[0][u'properties'], + self.assertTrue(len(channel) == 1 and self.Prop1Response in channel[0][u'properties'], 'Failed to create a test channel with property prop1') self.Prop1[u'value'] = '' channel[0][u'properties'] = [self.Prop1] @@ -1004,7 +1021,7 @@ def UserOwnerCheck(self): # Query Tests # =========================================================================== -class QueryTest(unittest.TestCase): +class QueryTest(ChannelFinderClientTestCase): def setUp(self): """Default Owners""" self.ChannelOwner = _testConf.get('DEFAULT', 'channelOwner') @@ -1026,7 +1043,7 @@ def testEmptyReturn(self): """ find for non existing entities should return None instead of a 404 """ - self.assertEquals(len(self.client.find(name=u'NonExistingChannelName')), 0, + self.assertEqual(len(self.client.find(name=u'NonExistingChannelName')), 0, 'Failed to return None when searching for a non existing channel') def MultiValueQuery(self): @@ -1111,7 +1128,7 @@ def MultiValueQuery(self): # =============================================================================== # ERROR tests # =============================================================================== -class ErrorTest(unittest.TestCase): +class ErrorTest(ChannelFinderClientTestCase): def setUp(self): """Default Owners""" self.ChannelOwner = _testConf.get('DEFAULT', 'channelOwner') diff --git a/test/testChannelUtil.py b/test/testChannelUtil.py index 25b9ae4..fe63279 100644 --- a/test/testChannelUtil.py +++ b/test/testChannelUtil.py @@ -14,7 +14,7 @@ urllib3.disable_warnings() -class Test(unittest.TestCase): +class ChannelUtilTest(unittest.TestCase): def setUp(self):