diff --git a/.env b/.env new file mode 100644 index 0000000..f465cdd --- /dev/null +++ b/.env @@ -0,0 +1,3 @@ +PYTHONPATH="./src" +TESTLINK_API_PYTHON_SERVER_URL="http://localhost:8085/lib/api/xmlrpc/v1/xmlrpc.php" +TESTLINK_API_PYTHON_DEVKEY="48072c25257af9f477a22c97a3858337" diff --git a/.gitignore b/.gitignore index 1a76690..e0ff092 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ build/ *.pyc dist/ MANIFEST +.pytest_cache/ diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 35e7301..0000000 --- a/.travis.yml +++ /dev/null @@ -1,24 +0,0 @@ -sudo: false -language: python -python: - - "2.6" - - "2.7" - - "3.3" - - "3.4" - - # command to install dependencies -install: - - if [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then pip install unittest2; fi - - if [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then pip install argparse; fi - - pip install . - -# command to run tests -# online tests uses TL connection, defined in -# TESTLINK_API_PYTHON_DEVKEY and TESTLINK_API_PYTHON_SERVER_URL -# see https://travis-ci.org/USER/TestLink-API-Python-client/settings/env_vars -# suggestion: use TL demo project with user pyTLapi, see tox.ini -script: - - py.test test/utest-offline - - if [[ $TESTLINK_API_PYTHON_SERVER_URL ]]; then py.test test/utest-online; fi -# see known problem: countTestCasesTS should handle the sufficient right errors #62 -# - if [[ $TESTLINK_API_PYTHON_SERVER_URL && $TRAVIS_PYTHON_VERSION == '3.4' ]]; then python example/TestLinkExample.py; fi diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..0bac8a5 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,11 @@ +{ + "python.autoComplete.extraPaths": [ + "${workspaceRoot}/src" + ], + "python.testing.pytestArgs": [ + "test" + ], + "python.testing.unittestEnabled": false, + "python.testing.pytestEnabled": true + +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..0fafc44 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,41 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=733558 + // for the documentation about the tasks.json format + "version": "2.0.0", + "tasks": [ + { + "label": "echo", + "type": "shell", + "command": "echo Hello" + }, + { + "label": "test Testlink Api sample", + "type": "process", + "detail": "Run sample Testlink Api Python Client communication", + "command": "${config:python.defaultInterpreterPath}", + "args": [ "${workspaceFolder}/example/${input:apiSample}.py" ], + "group": "test", + "presentation": { + "clear": true, + "panel": "dedicated" + }, + "problemMatcher": ["$python"], + "options": { + "cwd": "${workspaceFolder}", + "env": { "PYTHONPATH" : "./src", + "TESTLINK_API_PYTHON_SERVER_URL" : "http://localhost:8085/lib/api/xmlrpc/v1/xmlrpc.php", + "TESTLINK_API_PYTHON_DEVKEY" : "48072c25257af9f477a22c97a3858337" + } + } + } + ], + "inputs": [ + { + "type": "pickString", + "id": "apiSample", + "description": "Which TL API sample to run ?", + "options": ["TestLinkExample", "TestLinkExampleGenericApi","TestLinkExample_CF_KW","TestLinkExampleGenericApi_Req"], + "default": "TestLinkExample" + } + ] +} \ No newline at end of file diff --git a/CHANGES.rst b/CHANGES.rst index ec05c8e..c6453ef 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,15 +1,243 @@ Changes in TestLink-API-Python-client Source Distribution ========================================================= -TestLink-API-Python-client v0.6.2 release notes v0.6.2 (Oct. 2015) -------------------------------------------------------------------- +TestLink-API-Python-client v0.8.2 (under develop) +------------------------------------------------ +support for TL 1.9.20_fixed changes and py39 + +main topic is to support TL 1.9.20_fixed api changes + +implement 1.9.20_fixed new api interfaces - #141 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +new TestlinkAPIGeneric and TestlinkAPIClient api methods + +- createUser(, , , , [password=]) +- setUserRoleOnProject(, , ) + +new TestlinkAPIClient service methods + +- ensureUserExist(, [firstname=], [lastname=], + [email=, [password=]) +- ensureUserExistWithProjectRole(, , , + [firstname=], [lastname=], [email=, [password=]) + +implement 1.9.20_fixed changed api interfaces - #139 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +changed TestlinkAPIGeneric and TestlinkAPIClient api methods + +- createPlatform() is adapted to support new optional boolean arguments + and + - When they are not set to , assignTestCaseExecutionTask() might fail + with an error like + - TLResponseError: 3041: Test plan (name:TestPlan_API A) has no platforms linked + +TestLink-API-Python-client v0.8.1-fix131 (Mar. 2020) +------------------------------------------------------------ + +fix missing supported API 1.9.17 interfaces +- Pull request #131 by heuy - add closeBuild api function + +TestLink-API-Python-client v0.8.1 (Aug. 2019) +------------------------------------------------------------ +support for TL 1.9.20 (dev) release and py27, py36, py37 + +main topic is to support TL 1.9.19 and TL 1.9.20 (dev) api changes related to +test case attachments, which are stored since TL 1.9.19 with a reference to +the test case version instead the test case id . + +Parameter is now mandatory for _uploadTestCaseAttachment_ and optional +for _getTestCaseAttachments_. + +implement other 1.9.20 (dev) changed api interfaces - #122 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +changed TestlinkAPIGeneric and TestlinkAPIClient api methods + +- getTestCasesForTestPlan() is adapted to support the new optional argument . + Sample see ``_ + +known TL 1.9.19 issue: +~~~~~~~~~~~~~~~~~~~~~~~ +API-XMLRPC - getTestCaseAttachments returns no attachment, uploaded with uploadTestCaseAttachment + +- see TL Mantis Ticket 8658 `_ +- recommended to use the TL 1.9.20 development state with github commit + 6a4984164 or later (even for the TL upgrade or installation). + +known TL 1.9.20 development state issue (github commit a1c7aca97): +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Calling getTestCasesForTestPlan() with the new optional argument returns only internal db values. +As alternative, loop over all returned test cases and call getTestCaseCustomFieldDesignValue(). +Sample see ``_ + +TestLink-API-Python-client v0.8.0 (May. 2018) +--------------------------------------------- +support for TL 1.9.17 release and py27, py36 + + +implement 1.9.17 new api interfaces - #76, #81, #82, #83, #101 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +- getExecutionSet(, [testcaseid=], [testcaseexternalid=], + [buildid=], [buildname=], [platformid=], + [platformname=], [options=], [devKey=]) +- getRequirements(, [testplanid=], [platformid=], [devKey=]) +- getReqCoverage(, , [devKey=]) +- setTestCaseTestSuite(, , [devKey=]) +- getTestSuiteAttachments(, [devKey=]) +- getAllExecutionsResults(, [testcaseid=], + [testcaseexternalid=], [platformid=], + [buildid=], [options=]) + +TestReporter and other improvements pull request #94 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +pull request by Brian-Williams: + +- TestReporter's subclasses can be used to idempotently generate required + components of testlink before sending the report to a specified testcase. +- TestGenReporter is a default combination of all the TestReporter's subclasses + and will try to generate everything it's subclasses is capable of. +- Added TestLinkHelper._setParams to simplify libraries that need to overwrite + how a helper aquires it's parameters +- Added TestlinkAPIClient.getTestCaseByVersion to expose the common need of + gettting the latest testcase by default. It was already used, but not a + function in this class and has uses elsewhere. + +``_ includes sample, how to use +TestGenReporter and TestlinkAPIClient.getTestCaseByVersion + +Self signed / Let's Encrypt SSL certificate support #90 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +TestlinkAPIGeneric and TestlinkAPIClient accepts know the additional +xmlrpclib.ServerProxy() arguments *use_datetime* and *context* (new with Py2.7.9) +- https://docs.python.org/2/library/xmlrpclib.html + +*context* allows to define a SSL context, which can holds various data +longer-lived than single SSL connections, such as SSL configuration options, +certificate(s) and private key(s). (new with Py2.7.9) +- https://docs.python.org/2/library/ssl.html#ssl-contexts + +TestLinkHelper will set the *unverified_context()* if the server url starts with +*https* and no *context* is defined, calling +- TestLinkHelper().connect(TestlinkAPIClient) + + +known TL 1.9.17 issues: +~~~~~~~~~~~~~~~~~~~~~~~ +API-XMLRPC - new 1.9.17 xmlrpc.class function getAllExecutionsResults not callable via XMLRPC api + +- see `TL Mantis Ticket 8259 `_ + +fixed TL 1.9.17-DEV issues: +~~~~~~~~~~~~~~~~~~~~~~~~~~~ +API-XMLRPC - getRequirements raise PHP Fatal error (github commit 0c8feb6) + +- see `TL Mantis Ticket 7902 `_ + +API-XMLRPC - getExecutionSet raise database error (github commit 1ee5f78) + +- see `TL Mantis Ticket 7900 `_ + +TestLink-API-Python-client release notes v0.6.4 (Mar. 2017) +----------------------------------------------------------- +support for TL 1.9.16 release and py27, py34, py35 and py36 + +implement 1.9.16 new api interfaces - #80 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +new TestlinkAPIGeneric and TestlinkAPIClient api method + +- updateBuildCustomFieldsValues(, , , + , [devKey=]) + +example ``_ shows, how to set and get +customer field values + +TestLink-API-Python-client release notes v0.6.3 (Nov. 2016) +----------------------------------------------------------- +support for TL 1.9.15 release and py26, py27, py33, py34 and py35 + +- further releases will be developed only against py27, py34 and py35 +- If there is a need to support other py versions, please give feedback + +implement 1.9.15 new api interfaces - #54 #67 #69 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +new TestlinkAPIGeneric and TestlinkAPIClient api methods + +- updateTestSuite(, [testprojectid=], + [prefix=], [parentid=], [testsuitename=], + [details=
], [order=], [devKey=]) +- getTestSuite(, , [devKey=]) +- getIssueTrackerSystem(, [devKey=]) + +implement 1.9.15 changed api interfaces - #68 #70 #72 #71 #69 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +changed TestlinkAPIGeneric and TestlinkAPIClient api methods + +- reportTCResult() is adapted to support the new optional argument + for setting test step results +- createBuild() is adapted to support new optional arguments + + - : 1 (default) = activ 0 = inactiv + - : 1 (default) = open 1 = closed + - : YYYY-MM-DD + - : valid buildid tester assignments will be copied. + +- addTestCaseToTestPlan() is adapted to to support the new optional argument + to update linked Test Case Versions +- createTestCase() is adapted to to support the new optional arguments + and +- createTestProject() is adapted to to support the new optional arguments + and to link a project with an ITS + +examples: + + >>> tls = testlink.TestLinkHelper().connect(testlink.TestlinkAPIClient) + >>> tls.reportTCResult(None, 'aTPlanID', 'aBuildName', 'f', 'result note', + >>> testcaseexternalid='aTCaseFullExID', overwrite=True, + >>> platformname='Small Birds', execduration=4.1, + >>> timestamp='2015-09-19 14:33:02', + >>> steps=[{'step_number' : 3, 'result' : 'p', 'notes' : 'a exec note3'}, + >>> {'step_number' : 4, 'result' : 'f', 'notes' : 'a exec note4'}]) + >>> tls.createBuild(aTPlanID, 'newBuildName', 'a build note', + >>> active=1, open=1, releasedate='2016-11-30' + >>> copytestersfrombuild=existingBuildID) + >>> tls.addTestCaseToTestPlan(aTProjectID, aTPlanID, 'aTCaseFullExID', + >>> aTCVersion, overwrite=1) + + +known TL 1.9.15 issues: +~~~~~~~~~~~~~~~~~~~~~~~ + +changing test suite order with updateTestSuite raise internal server error + +- same reason as `TL Mantis Ticket 7696 `_ +- solution: change *testlink-1.9.15/lib/functions/testsuite.class.php - update* as + descriped in `TL GitHub Commit 1fa41e7 `_ + +TestLink web presents no login page (internal server error) + +- see `TL Mantis Ticket 7708 `_ +- solution: change *testlink-1.9.15/lib/functions/common.php* as described in `TL GitHub Commit db74644 `_ + +Test projects with execution step results can not be deleted +- details and solution see `TL Mantis Ticket 7765 `_ + +TestLink-API-Python-client v0.6.2 release notes v0.6.2 (Oct. 2015) +------------------------------------------------------------------ support for TL 1.9.14 release and py26, py27, py33 and py34 - further releases will be developed only against py27 and py34. - If there is a need to support other py versions, please give feedback implement 1.9.14 new api interfaces - #53 #61 -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ new TestlinkAPIGeneric and TestlinkAPIClient api methods @@ -24,7 +252,7 @@ example ``_ shows, how to set and get customer field values implement 1.9.14 changed api interfaces - #48 #49 #54 #59 -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ changed TestlinkAPIGeneric and TestlinkAPIClient api methods @@ -61,7 +289,7 @@ a different error code - 1.9.13 error code *5000* - 1.9.14 error code *5040* Bugfixes TestLink-API-Python-client v0.6.1 - #51 #55 #56 #45 -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ whatArgs reports incorrect arg name for createTestCase @@ -87,11 +315,11 @@ Known TL 1.9.14 limitations: - 7282 updateTestSuiteCustomFieldDesignValue() does change customer field values -TestLink-API-Python-client release notes v0.6.1 (Mar. 2015) ------------------------------------------------------------- +TestLink-API-Python-client release notes v0.6.1 (Mar. 2015) +----------------------------------------------------------- support for TL 1.9.13 release -Proxy configuration support in TestLinkHelper - pull request #36 +Proxy configuration support in TestLinkHelper - pull request #36 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Integrates `Maberi `_ @@ -102,7 +330,7 @@ pull request `#36 ` - Recognizes "http_proxy" environment variable. implement 1.9.13 new api methods #32 #41 #42 #44 #47 #46 -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ new TestlinkAPIGeneric and TestlinkAPIClient api methods @@ -130,7 +358,7 @@ new TestlinkAPIGeneric and TestlinkAPIClient api methods examples see ``_ and ``_ implement 1.9.13 api change - getTestCasesForTestPlan #41 -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ TestlinkAPIGeneric and TestlinkAPIClient api method getTestCasesForTestPlan() accepts now the additional optional argument platformid= @@ -144,13 +372,13 @@ example: Also the optional argument buildid= could now be used -TestLink-API-Python-client release notes v0.6.0 (Dec. 2014) ------------------------------------------------------------- +TestLink-API-Python-client release notes v0.6.0 (Dec. 2014) +----------------------------------------------------------- support for TestLink release 1.9.12 and py26, py27, py33 and py34 python 3 support - pull requests #33 #37 -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Integrates `manojklm `_ pull requests @@ -209,7 +437,7 @@ new TestlinkAPIGeneric and TestlinkAPIClient api method examples see ``_ -TestLink-API-Python-client release notes v0.5.1 (Aug. 2014) +TestLink-API-Python-client release notes v0.5.1 (Aug. 2014) ----------------------------------------------------------- support for TestLink release 1.9.11 @@ -239,7 +467,7 @@ new TestlinkAPIGeneric and TestlinkAPIClient api method examples see ``_ -TestLink-API-Python-client release notes v0.5.0 (Jul. 2014) +TestLink-API-Python-client release notes v0.5.0 (Jul. 2014) ----------------------------------------------------------- support for TestLink release 1.9.10 @@ -439,7 +667,7 @@ Other API methods can be used with the new method - callServerWithPosArgs(apiMethodame, [apiArgName=apiArgValue]) -generic api class TestlinkAPIGeneric #7 +generic api class TestlinkAPIGeneric #7 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ new class TestlinkAPIGeneric implements the Testlink API methods as generic PY methods @@ -470,20 +698,20 @@ helper method .whatArgs(apiMethodName) #8 The Teslink API Client can now be asked, what arguments a API method expects:: - import testlink - tlh = testlink.TestLinkHelper() - tls = tlh.connect(testlink.TestlinkAPIClient) - print tls.whatArgs('createTestPlan') - createTestPlan(, , [note=], [active=], [public=], [devKey=]) - create a test plan + import testlink + tlh = testlink.TestLinkHelper() + tls = tlh.connect(testlink.TestlinkAPIClient) + print tls.whatArgs('createTestPlan') + createTestPlan(, , [note=], [active=], [public=], [devKey=]) + create a test plan or for a description of all implemented api method :: - import testlink - tlh = testlink.TestLinkHelper() - tls = tlh.connect(testlink.TestlinkAPIClient) - for m in testlink.testlinkargs._apiMethodsArgs.keys(): - print tls.whatArgs(m), '\n' + import testlink + tlh = testlink.TestLinkHelper() + tls = tlh.connect(testlink.TestlinkAPIClient) + for m in testlink.testlinkargs._apiMethodsArgs.keys(): + print tls.whatArgs(m), '\n' other changes ~~~~~~~~~~~~~ diff --git a/README.rst b/README.rst index 0c6a215..d8f5856 100644 --- a/README.rst +++ b/README.rst @@ -1,17 +1,11 @@ TestLink API Python Client ========================== -Copyright 2011-2015 +Copyright 2011-2022 James Stock, Olivier Renault, Luiko Czub, TestLink-API-Python-client developers License `Apache License 2.0`_ -.. image:: https://travis-ci.org/lczub/TestLink-API-Python-client.svg?branch=master - :target: https://travis-ci.org/lczub/TestLink-API-Python-client - -.. contents:: - :local: - Introduction ------------ @@ -66,7 +60,7 @@ src/ Source for TestLink API Python Client doc/ - `Installation`_ and `Usage`_ documentation + `Installation`_ and `Usage (EN)`_ / `Usage (FR)`_ documentation examples/ Examples, how to use `TestlinkAPIGeneric`_ and `TestlinkAPIClient`_. @@ -93,7 +87,8 @@ TestLink-API-Python-client developers ------------------------------------- * `James Stock`_, `Olivier Renault`_, `lczub`_, `manojklm`_ (PY3) * `g4l4drim`_, `pade`_, `anton-matosov`_, `citizen-stig`_, `charz`_, `Maberi`_ -* anyone forgotten? +* `Brian-Williams`_, `alexei-drozdov`_, `janLo`_, `heuj`_, `elapfra`_ +* `Mikycid`_, anyone forgotten? .. _Apache License 2.0: http://www.apache.org/licenses/LICENSE-2.0 .. _TestLink: http://testlink.org @@ -101,7 +96,8 @@ TestLink-API-Python-client developers .. _Robot Framework: http://code.google.com/p/robotframework .. _Jenkins: http://jenkins-ci.org .. _Installation: doc/install.rst -.. _Usage: doc/usage.rst +.. _Usage (EN): doc/usage.rst +.. _Usage (FR): doc/fr_usage.rst .. _TestlinkAPIGeneric: example/TestLinkExampleGenericApi.py .. _TestlinkAPIClient: example/TestLinkExample.py .. _tox.ini: tox.ini @@ -116,4 +112,10 @@ TestLink-API-Python-client developers .. _citizen-stig: https://github.com/citizen-stig/TestLink-API-Python-client .. _charz: https://github.com/charz/TestLink-API-Python-client.git .. _manojklm: https://github.com/manojklm/TestLink-API-Python-client -.. _Maberi: https://github.com/Maberi/TestLink-API-Python-client.git +.. _Maberi: https://github.com/Maberi/TestLink-API-Python-client +.. _Brian-Williams: https://github.com/Brian-Williams/TestLink-API-Python-client +.. _alexei-drozdov: https://github.com/alexei-drozdov/TestLink-API-Python-client +.. _janLo: https://github.com/janLo/TestLink-API-Python-client +.. _heuj: https://github.com/heuj/TestLink-API-Python-client +.. _elapfra: https://github.com/elapfra/TestLink-API-Python-client +.. _Mikycid: https://github.com/Mikycid/TestLink-API-Python-client \ No newline at end of file diff --git a/doc/fr_usage.rst b/doc/fr_usage.rst new file mode 100644 index 0000000..366e94a --- /dev/null +++ b/doc/fr_usage.rst @@ -0,0 +1,229 @@ +TestLink-API-Python-client Usage +================================ + +.. contents:: + :local: + +Comment communiquer avec testlink dans une interface système python +------------------------------------------- + +Se connecter à TestLink, compter les projets existants et récupérer les données d'un cas de test: :: + + [PYENV]\testlink\Scripts\activate + set TESTLINK_API_PYTHON_SERVER_URL=http://[YOURSERVER]/testlink/lib/api/xmlrpc/v1/xmlrpc.php + set TESTLINK_API_PYTHON_DEVKEY=[Users devKey generated by TestLink] + python + >>> import testlink + >>> tls = testlink.TestLinkHelper().connect(testlink.TestlinkAPIClient) + >>> tls.countProjects() + 3 + >>> tls.getTestCase(None, testcaseexternalid='NPROAPI3-1') + [{'full_tc_external_id': 'NPROAPI3-1', 'node_order': '0', 'is_open': '1', 'id': '2757', ...}] + +Demander au TestLink API Client quels arguments attend une des méthodes de l'API: :: + + import testlink + tlh = testlink.TestLinkHelper() + tls = tlh.connect(testlink.TestlinkAPIClient) + print tls.whatArgs('createTestPlan') + > createTestPlan(, , [note=], [active=], + [public=], [devKey=]) + > create a test plan + +Générer une description de toutes les méthodes implémentées par l'API: :: + + import testlink + tlh = testlink.TestLinkHelper() + tls = tlh.connect(testlink.TestlinkAPIClient) + for m in testlink.testlinkargs._apiMethodsArgs.keys(): + print(tls.whatArgs(m), '\n') + +Copier les cas de test +--------------- + +Copier un cas de test dans une autre suite en changeant son nom:: + + >>> import testlink + >>> tls = testlink.TestLinkHelper().connect(testlink.TestlinkAPIClient) + >>> tc_info = tls.getTestCase(None, testcaseexternalid='NPROAPI-3') + [{'full_tc_external_id': 'NPROAPI-3', ..., 'id': '5440', 'version': '2', + 'testsuite_id': '5415', 'tc_external_id': '3','testcase_id': '5425', ...}] + >>> tls.copyTCnewTestCase(tc_info[0]['testcase_id'], testsuiteid=newSuiteID, + testcasename='a new test case name') + +Créer une nouvelle version d'un cas de test en changeant sa description et son importance:: + + >>> import testlink + >>> tls = testlink.TestLinkHelper().connect(testlink.TestlinkAPIClient) + >>> tc_info = tls.getTestCase(None, testcaseexternalid='NPROAPI-3') + [{'full_tc_external_id': 'NPROAPI-3', ..., 'id': '5440', 'version': '2', + 'testsuite_id': '5415', 'tc_external_id': '3','testcase_id': '5425', ...}] + >>> tls.copyTCnewVersion(tc_info[0]['testcase_id'], summary='new summary', + importance='1') + + +Par défaut, la dernière version d'un cas de test sera utilisée pour la copie. +Si une autre version doit être copiée, il est possible de spécifier la version +attendue en tant que deuxième argument. Example:: + + >>> tls.copyTCnewTestCase(tc_info[0]['testcase_id'], 1, testsuiteid=newSuiteID, + testcasename='a new test case name') + >>> tls.copyTCnewVersion(tc_info[0]['testcase_id'], 1, summary='new summary', + importance='1') + +Rapporter les résultats du test +------------------- + +En utilisant la classe TestlinkAPIClient - exemple d'un cas de test échoué +sans auteur (l'argument 'user' n'est utilisable qu'à partir d'une version +de TestLink de 1.9.10 ou supérieure): + + >>> import testlink + >>> tls = testlink.TestLinkHelper().connect(testlink.TestlinkAPIClient) + >>> tls.reportTCResult(a_TestCaseID, a_TestPlanID, 'a build name', 'f', + 'some notes', + user='a user login name', platformid=a_platformID) + + +En utilisant la classe TestlinkAPIGeneric - exemple d'un cas de test passé +en utilisant un auteur (argument 'user'): + + >>> import testlink + >>> tls = testlink.TestLinkHelper().connect(testlink.TestlinkAPIGeneric) + >>> tls.reportTCResult(a_TestPlanID, 'p', testcaseid=a_TestCaseID, + buildname='a build name', notes='some notes', + user='a login name', platformid=a_platformID) + + +En utilisant la classe TestlinkAPIGeneric - exemple d'un cas de test bloqué +sans auteur + + >>> import testlink + >>> tls = testlink.TestLinkHelper().connect(testlink.TestlinkAPIGeneric) + >>> exTCID = tls.getTestCase(testcaseid=a_TestCaseID)[0]['full_tc_external_id'] + >>> tls.reportTCResult(a_TestPlanID, 'b', testcaseexternalid=exTCID, + buildid='a build name', platformname='a platform name') + +Rapport de résultats de tests avec horodatage et résultat des étapes +-------------------------------------------------- + +Ce résultat de test utilise son id externe (testcaseexternalid), et non l'id interne (testcaseid) + +- Les arguments 'execduration' et 'timestamp' requièrent une version de + TestLink de 1.9.14 ou supérieure +- L'argument 'steps' requiert une version de TestLink de 1.9.15 ou supérieure + + >>> import testlink + >>> tls = testlink.TestLinkHelper().connect(testlink.TestlinkAPIClient) + >>> tls.reportTCResult(None, newTestPlanID_A, None, 'f', '', guess=True, + testcaseexternalid=tc_aa_full_ext_id, platformname=NEWPLATFORM_A, + execduration=3.9, timestamp='2015-09-18 14:33', + steps=[{'step_number' : 6, 'result' : 'p', 'notes' : 'result note for passed step 6'}, + {'step_number' : 7, 'result' : 'f', 'notes' : 'result note for failed step 7'}] ) + +Envoyer des pièces jointes +------------------ + +Télécharger des pièces jointes peut être fait de deux différentes manières: + +Avec un descripteur de fichier : + + a_file_obj=open(CHEMIN_VALIDE_VERS_LE_FICHIER) + newAttachment = myTestLink.uploadExecutionAttachment(a_file_obj, A_Result_ID, + 'Attachment Title', 'Attachment Description') + + +Ou avec un chemin de fichier : + + a_file_path=A_VALID_FILE_PATH + newAttachment = myTestLink.uploadExecutionAttachment(CHEMIN_VALIDE_VERS_LE_FICHIER, A_Result_ID, + 'Attachment Title', 'Attachment Description') + +Lister les mots-clés +------------- + +En utilisant une méthode de l'API (classe TestlinkAPIGeneric) - +Lister les mots-clés de tous les cas de test d'une suite: + + >>> import testlink + >>> tls = testlink.TestLinkHelper().connect(testlink.TestlinkAPIClient) + >>> ts_kw = tls.getTestCasesForTestSuite(SuiteID, False, 'full', getkeywords=True) + + +En utilisant une méthode de l'API (classe TestlinkAPIGeneric) - +Lister tous les mots clés d'une suite de test et ses sous-suites + + >>> ts_kw = tls.getTestCasesForTestSuite(SuiteID, True, 'full', getkeywords=True) + +En utilisant une méthode du service (classe TestlinkAPIClient) - +Lister tous les mots clés sans ses détails pour un cas de test + + >>> tc_kw = tls.listKeywordsForTC(5440) + >>> tc_kw = tls.listKeywordsForTC('NPROAPI-3') + +En utilisant une méthode du service (classe TestlinkAPIClient) - +Lister tous les mots clés sans ses détails pour tous les cas de test d'une suite + + >>> ts_kw = tls.listKeywordsForTS('5415') + + +Lancement d'un exemple +------------ + +Pour lancer l'exemple "comment utiliser la classe TestlinkAPIClient", en +spécifiant les paramètres de connexion en tant qu'arguments de ligne de commande [1]_: :: + + [PYENV]\testlink\Scripts\activate + python example\TestLinkExample.py + --server_url http://[YOURSERVER]/testlink/lib/api/xmlrpc.php + --devKey [Users devKey generated by TestLink] + +Pour lancer l'exemple "comment utiliser la classe TestlinkAPIGeneric", en +spécifiant les paramètres de connexion en tant que variable d'environment [2]_: :: + + [PYENV]\testlink\Scripts\activate + set TESTLINK_API_PYTHON_SERVER_URL=http://[YOURSERVER]/testlink/lib/api/xmlrpc/v1/xmlrpc.php + set TESTLINK_API_PYTHON_DEVKEY=[Users devKey generated by TestLink] + python example\TestLinkExampleGenericApi.py + +.. [1] TestLinkExample.py creates a new test project NEW_PROJECT_API-[CountProjects+1]. +.. [2] TestLinkExampleGenericApi.py creates a new test project PROJECT_API_GENERIC-[CountProjects+1]. + +Lancer des tests unitaires +------------- + +Lancer des tests unitaires avec interaction du serveur de TestLink: :: + + [PYENV]\testlink\Scripts\activate + set TESTLINK_API_PYTHON_SERVER_URL=http://[YOURSERVER]/testlink/lib/api/xmlrpc.php + set TESTLINK_API_PYTHON_DEVKEY=[Users devKey generated by TestLink] + cd test\utest + python -m unittest discover -s test\utest-online + +Lancer des tests unitaires sans interaction du serveur de TestLink: :: + + [PYENV]\testlink\Scripts\activate + cd test\utest + python -m unittest discover -s test\utest-offline + +En deca de Py26, unittest2_ doit être utilisé. + +.. _unittest2: https://pypi.python.org/pypi/unittest2 + + +Comment accéder aux données originelles d'échange de XML +------------------------------------------ + +Si pour des raisons de débogage les versions originelles d'échange de XML sont requises, +il est possible d'initialiser l'API client avec le paramètre optionnel *verbose* mis à *True*: :: + + >>> tlh = testlink.TestLinkHelper() + >>> tls = testlink.TestlinkAPIClient(tlh._server_url, tl._devkey, verbose=True) + send: b"POST /testlink/lib/api/xmlrpc/v1/xmlrpc.php HTTP/1.1\r\nHost: ... + \n\ntl.getUserByLogin\n...\n\n" + reply: 'HTTP/1.1 200 OK\r\n' + header: Date header: Server header: ... body: b'\n\n ...' + body: b'1\n\n \n ...' + body: b'... \n\n' + + diff --git a/doc/install.rst b/doc/install.rst index a55cb3f..92a50c6 100644 --- a/doc/install.rst +++ b/doc/install.rst @@ -7,14 +7,8 @@ TestLink-API-Python-client Installation Preconditions ------------- -Currently the combinations Python 2.6.9/2.7.9/3.3.5/3.4.2 and TestLink 1.9.14 are tested. - -- Other combination might work - feedback is welcome :-) - -To use TestLink-API-Python-client under Py26, the module **argparse** must be -installed additionally (Py27, Py33 and Py34 already includes this):: - - pip install argparse +Currently the combinations Python 2.7.16 / 3.6.8 / 3.7.4 are tested with +TestLink 1.9.20 (development state, github a1c7aca97). Other combination might work - feedback is welcome :-) TestLink configuration ---------------------- @@ -48,7 +42,7 @@ The source code can be retrieved as source distribution either Install the archives using pip by running:: - pip install TestLink-API-Python-client-0.4.7.zip + pip install TestLink-API-Python-client-0.8.1.zip Installing from source ---------------------- @@ -110,12 +104,6 @@ with connection parameter as environment variables - TESTLINK_API_PYTHON_SERVER_URL and TESTLINK_API_PYTHON_DEVKEY -Known Installation Problems ----------------------------- - -If the installation for the combination **linux + py26** fails, maybe the workaround from `issue 50`_ helps. - - .. _PyPI: https://pypi.python.org/pypi .. _pip: http://www.pip-installer.org diff --git a/doc/usage.rst b/doc/usage.rst index b577604..9468106 100644 --- a/doc/usage.rst +++ b/doc/usage.rst @@ -104,6 +104,7 @@ Report test results with timestamp and step result -------------------------------------------------- This test result uses the external test case id and not the internal. + - argument 'execduration' and 'timestamp' usable with TL >= 1.9.14: - argument 'steps' usable with TL >= 1.9.15: @@ -112,8 +113,8 @@ This test result uses the external test case id and not the internal. >>> tls.reportTCResult(None, newTestPlanID_A, None, 'f', '', guess=True, testcaseexternalid=tc_aa_full_ext_id, platformname=NEWPLATFORM_A, execduration=3.9, timestamp='2015-09-18 14:33', - steps=[{'step_number' : 6, 'result' : 'p', 'notes" : 'result note for passed step 6'}, - {'step_number' : 7, 'result' : 'f', 'notes" : 'result note for failed step 7'}] ) + steps=[{'step_number' : 6, 'result' : 'p', 'notes' : 'result note for passed step 6'}, + {'step_number' : 7, 'result' : 'f', 'notes' : 'result note for failed step 7'}] ) Upload attachments ------------------ diff --git a/example/Last7DaysTestCases.py b/example/Last7DaysTestCases.py index 362c88d..6607469 100644 --- a/example/Last7DaysTestCases.py +++ b/example/Last7DaysTestCases.py @@ -1,7 +1,7 @@ #! /usr/bin/python # -*- coding: UTF-8 -*- -# Copyright 2011-2012 Olivier Renault, James Stock, TestLink-API-Python-client developers +# Copyright 2011-2019 Olivier Renault, James Stock, TestLink-API-Python-client developers # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -30,7 +30,7 @@ def iterTCasesfromTProject(api, TProjName, date1, date2): """ returns as iterator all test cases of project TPROJTNAME, which are created between DATE1 and DATE2 DATE1 and DATE2 must be of type time.struct_time """ - TProjId = api.getTestProjectByName(TProjName)[0]['id'] + TProjId = api.getTestProjectByName(TProjName)['id'] for TSinfo in api.getFirstLevelTestSuitesForTestProject(TProjId): TSuiteId = TSinfo['id'] for TCid in api.getTestCasesForTestSuite(TSuiteId, deep=1,details='only_id'): diff --git a/example/TestLinkExample.py b/example/TestLinkExample.py index f2c0a89..ed10ef9 100644 --- a/example/TestLinkExample.py +++ b/example/TestLinkExample.py @@ -1,7 +1,7 @@ #! /usr/bin/python # -*- coding: UTF-8 -*- -# Copyright 2011-2015 Olivier Renault, Luiko Czub, TestLink-API-Python-client developers +# Copyright 2011-2021 Olivier Renault, Luiko Czub, TestLink-API-Python-client developers # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -56,7 +56,7 @@ """ from __future__ import print_function -from testlink import TestlinkAPIClient, TestLinkHelper +from testlink import TestlinkAPIClient, TestLinkHelper, TestGenReporter from testlink.testlinkerrors import TLResponseError import sys, os.path from platform import python_version @@ -88,9 +88,11 @@ NEWTESTPLAN_A="TestPlan_API A" NEWTESTPLAN_B="TestPlan_API B" NEWTESTPLAN_C="TestPlan_API C - DeleteTest" +NEWTESTPLAN_G="TestPlan_API G - generated" NEWPLATFORM_A='Big Birds %s' % myPyVersionShort NEWPLATFORM_B='Small Birds' NEWPLATFORM_C='Ugly Birds' +NEWPLATFORM_G='generated Birds' NEWTESTSUITE_A="A - First Level" NEWTESTSUITE_B="B - First Level" NEWTESTSUITE_AA="AA - Second Level" @@ -100,6 +102,8 @@ NEWBUILD_A='%s' % myApiVersion NEWBUILD_B='%s' % myApiVersion NEWBUILD_C='%s - DeleteTest' % myApiVersion +NEWBUILD_D='%s - copyTestersTest' % myApiVersion +NEWBUILD_G='%s - generated' % myApiVersion this_file_dirname=os.path.dirname(__file__) NEWATTACHMENT_PY= os.path.join(this_file_dirname, 'TestLinkExample.py') @@ -111,14 +115,22 @@ NEWPROJECT="NEW_PROJECT_API-%s" % myPyVersionShort NEWPREFIX="NPROAPI%s" % myPyVersionShort +ITSNAME="myITS" # used connection settings print(myTestLink.connectionInfo()) print("") -# CHANGE this name into a valid account, known in your TL application -myTestUserName="pyTLapi" -myTestUserName2="admin" +# ensure tester and expert users exists +myTestUserName="myTester" +myTestUser1_ID=myTestLink.ensureUserExist(myTestUserName, + firstname="myFirstName", lastname="myLastName", mail="myTester@example.com") +print("ensureUserExist", myTestUserName, myTestUser1_ID) + +myTestUserName2="myExpert" +myTestUser2_ID=myTestLink.ensureUserExist(myTestUserName2) +print("checkUser", myTestUserName2, myTestUser2_ID) + # get user information response = myTestLink.getUserByLogin(myTestUserName) print("getUserByLogin", response) @@ -155,18 +167,33 @@ print("deleteTestProject", response) except TLResponseError: print("No project with prefix %s exists" % NEWPREFIX) + +# # get IssueTrackerSystem +# aITS=myTestLink.getIssueTrackerSystem(aITSNAME) +# print("getIssueTrackerSystem", aITS) # Creates the project projInfo = 'Example created with Python %s API class %s in TL %s' % \ ( python_version(), myApiVersion, myTLVersion ) newProject = myTestLink.createTestProject(NEWPROJECT, NEWPREFIX, notes=projInfo, active=1, public=1, +# itsname=ITSNAME, itsenabled=1, options={'requirementsEnabled' : 0, 'testPriorityEnabled' : 1, 'automationEnabled' : 1, 'inventoryEnabled' : 0}) print("createTestProject", newProject) newProjectID = newProject[0]['id'] print("New Project '%s' - id: %s" % (NEWPROJECT,newProjectID)) +# assign project roles to user 1 and get user information +response = myTestLink.ensureUserExistWithProjectRole(myTestUserName, "tester", NEWPROJECT) +print("ensureUserExistWithProjectRole user1 role tester", response) + +# assign project roles to user 2 and get user information +response = myTestLink.ensureUserExistWithProjectRole(myTestUserName2, "senior tester", NEWPROJECT) +print("ensureUserExistWithProjectRole user2 role senior tester", response) +response = myTestLink.ensureUserExistWithProjectRole(myTestUserName2, "test designer", NEWPROJECT) +print("ensureUserExistWithProjectRole user2 role test designer", response) + # Creates the test plan newTestPlan = myTestLink.createTestPlan(NEWTESTPLAN_A, testprojectname=NEWPROJECT, notes='New TestPlan created with the API',active=1, public=1) @@ -184,7 +211,8 @@ # Create platform 'Big Birds x' newPlatForm = myTestLink.createPlatform(NEWPROJECT, NEWPLATFORM_A, - notes='Platform for Big Birds, unique name, only used in this project') + notes='Platform for Big Birds, unique name, only used in this project', + platformondesign=True, platformonexecution=True) print("createPlatform", newPlatForm) newPlatFormID_A = newPlatForm['id'] # Add Platform 'Big Bird x' to platform @@ -193,7 +221,8 @@ # Create platform 'Small Birds' newPlatForm = myTestLink.createPlatform(NEWPROJECT, NEWPLATFORM_B, - notes='Platform for Small Birds, name used in all example projects') + notes='Platform for Small Birds, name used in all example projects', + platformondesign=True, platformonexecution=True) print("createPlatform", newPlatForm) newPlatFormID_B = newPlatForm['id'] # Add Platform 'Small Bird' to platform @@ -202,7 +231,8 @@ # Create platform 'Ugly Birds' newPlatForm = myTestLink.createPlatform(NEWPROJECT, NEWPLATFORM_C, - notes='Platform for Ugly Birds, will be removed from test plan') + notes='Platform for Ugly Birds, will be removed from test plan', + platformondesign=True, platformonexecution=True) print("createPlatform", newPlatForm) newPlatFormID_C = newPlatForm['id'] # Add Platform 'Ugly Bird' to platform @@ -234,8 +264,13 @@ MANUAL = 1 AUTOMATED = 2 +READFORREVIEW=2 +REWORK=4 +HIGH=3 +MEDIUM=2 +LOW=1 -#Creates the test case TC_AA +#Creates the test case TC_AA with state ready for review myTestLink.initStep("Step action 1", "Step result 1", MANUAL) myTestLink.appendStep("Step action 2", "Step result 2", MANUAL) myTestLink.appendStep("Step action 3", "Step result 3", MANUAL) @@ -246,24 +281,31 @@ newTestCase = myTestLink.createTestCase(NEWTESTCASE_AA, newTestSuiteID_AA, newProjectID, myTestUserName, "This is the summary of the Test Case AA", - preconditions='these are the preconditions') + preconditions='these are the preconditions', + importance=LOW, state=READFORREVIEW, estimatedexecduration=10.1) print("createTestCase", newTestCase) newTestCaseID_AA = newTestCase[0]['id'] print("New Test Case '%s' - id: %s" % (NEWTESTCASE_AA, newTestCaseID_AA)) -#Creates the test case TC_B +#Creates the test case TC_B with state rework - in wrong test suite A myTestLink.initStep("Step action 1", "Step result 1", AUTOMATED) myTestLink.appendStep("Step action 2", "Step result 2", AUTOMATED) myTestLink.appendStep("Step action 3", "Step result 3", AUTOMATED) myTestLink.appendStep("Step action 4", "Step result 4", AUTOMATED) myTestLink.appendStep("Step action 5", "Step result 5", AUTOMATED) -newTestCase = myTestLink.createTestCase(NEWTESTCASE_B, newTestSuiteID_B, +newTestCase = myTestLink.createTestCase(NEWTESTCASE_B, newTestSuiteID_A, newProjectID, myTestUserName, "This is the summary of the Test Case B", - preconditions='these are the preconditions', executiontype=AUTOMATED) -print("createTestCase", newTestCase) + preconditions='these are the preconditions', executiontype=AUTOMATED, + status=REWORK, estimatedexecduration=0.5) +print("createTestCase TC-B in TS-A", newTestCase) newTestCaseID_B = newTestCase[0]['id'] print("New Test Case '%s' - id: %s" % (NEWTESTCASE_B, newTestCaseID_B)) + +# Move test case TC_B to correct test suite B +tc_b_full_ext_id = myTestLink.getTestCase(newTestCaseID_B)[0]['full_tc_external_id'] +response = myTestLink.setTestCaseTestSuite(tc_b_full_ext_id, newTestSuiteID_B) +print("setTestCaseTestSuite TC-B to TS-B" , response) # Add test cases to test plan - we need the full external id ! # for every test case version 1 is used @@ -298,7 +340,7 @@ {'step_number' : 6, 'actions' : "Step action 6 -b added by updateTestCase" , 'expected_results' : "Step result 6 - b added", 'execution_type' : AUTOMATED}) response = myTestLink.updateTestCase(tc_b_full_ext_id, version=tc_version, - steps=steps_tc_b_v1u, importance='high', estimatedexecduration=3) + steps=steps_tc_b_v1u, importance=MEDIUM, estimatedexecduration=3) print("updateTestCase", response) # create additional steps via createTestCaseSteps - action create @@ -335,7 +377,8 @@ print("removePlatformFromTestPlan", response) # -- Create Build for TestPlan A (uses platforms) -newBuild = myTestLink.createBuild(newTestPlanID_A, NEWBUILD_A, 'Notes for the Build') +newBuild = myTestLink.createBuild(newTestPlanID_A, NEWBUILD_A, + 'Notes for the Build', releasedate="2016-12-31") print("createBuild", newBuild) newBuildID_A = newBuild[0]['id'] print("New Build '%s' - id: %s" % (NEWBUILD_A, newBuildID_A)) @@ -373,12 +416,14 @@ testcaseexternalid=tc_aa_full_ext_id) print("getTestCaseBugs TC_AA in TP_A (TC is not executed)", response) -# report Test Case Results for platform 'Big Bird' +# report Test Case Results for platform 'Big Bird' with step results # TC_AA failed, build should be guessed, TC identified with external id newResult = myTestLink.reportTCResult(None, newTestPlanID_A, None, 'f', '', guess=True, testcaseexternalid=tc_aa_full_ext_id, platformname=NEWPLATFORM_A, - execduration=3.9, timestamp='2015-09-18 14:33') + execduration=3.9, timestamp='2015-09-18 14:33', + steps=[{'step_number' : 3, 'result' : 'p', 'notes' : 'result note for passed step 3'}, + {'step_number' : 4, 'result' : 'f', 'notes' : 'result note for failed step 4'}] ) print("reportTCResult", newResult) newResultID_AA = newResult[0]['id'] @@ -401,24 +446,30 @@ print("reportTCResult", newResult) newResultID_B = newResult[0]['id'] -# add this (text) file as Attachemnt to last execution of TC_B with -# different filename 'MyPyExampleApiClient.py' -a_file=open(NEWATTACHMENT_PY) -newAttachment = myTestLink.uploadExecutionAttachment(a_file, newResultID_B, - 'Textfile Example', 'Text Attachment Example for a TestCase Execution', - filename='MyPyExampleApiClient.py') -print("uploadExecutionAttachment", newAttachment) -# add png file as Attachemnt to last execution of TC_AA -# !Attention - on WINDOWS use binary mode for none text file -# see http://docs.python.org/2/tutorial/inputoutput.html#reading-and-writing-files -a_file=open(NEWATTACHMENT_PNG, mode='rb') -newAttachment = myTestLink.uploadExecutionAttachment(a_file, newResultID_AA, - 'PNG Example', 'PNG Attachment Example for a TestCase Execution') -print("uploadExecutionAttachment", newAttachment) +#FIXME: know 1.9.20_fixed issue +# testlink.testlinkerrors.TLResponseError: 6002: (uploadExecutionAttachment) - Error inserting attachment on DB +# +# E_WARNING base64_decode() expects parameter 1 to be string, array given +# - in /var/www/html/lib/api/xmlrpc/v1/xmlrpc.class.php - Line 5834 +# +# # add this (text) file as Attachemnt to last execution of TC_B with +# # different filename 'MyPyExampleApiClient.py' +# a_file=open(NEWATTACHMENT_PY) +# newAttachment = myTestLink.uploadExecutionAttachment(a_file, newResultID_B, +# 'Textfile Example', 'Text Attachment Example for a TestCase Execution', +# filename='MyPyExampleApiClient.py') +# print("uploadExecutionAttachment", newAttachment) +# # add png file as Attachemnt to last execution of TC_AA +# # !Attention - on WINDOWS use binary mode for none text file +# # see http://docs.python.org/2/tutorial/inputoutput.html#reading-and-writing-files +# a_file=open(NEWATTACHMENT_PNG, mode='rb') +# newAttachment = myTestLink.uploadExecutionAttachment(a_file, newResultID_AA, +# 'PNG Example', 'PNG Attachment Example for a TestCase Execution') +# print("uploadExecutionAttachment", newAttachment) # -- Create Build for TestPlan B (uses no platforms) newBuild = myTestLink.createBuild(newTestPlanID_B, NEWBUILD_B, - 'Build for TestPlan without platforms') + 'Build for TestPlan without platforms', releasedate='2016-11-30') print("createBuild", newBuild) newBuildID_B = newBuild[0]['id'] print("New Build '%s' - id: %s" % (NEWBUILD_B, newBuildID_B)) @@ -531,10 +582,17 @@ platformname=NEWPLATFORM_C) print("reportTCResult", newResult) newResultID_B = newResult[0]['id'] -newAttachment = myTestLink.uploadExecutionAttachment(NEWATTACHMENT_PY, newResultID_B, - 'Textfile Example', 'Attachment Example for a TC Execution and TP delete test', - filename='MyPyTPDeleteTest.py') -print("uploadExecutionAttachment", newAttachment) + +#FIXME: know 1.9.20_fixed issue +# testlink.testlinkerrors.TLResponseError: 6002: (uploadExecutionAttachment) - Error inserting attachment on DB +# +# E_WARNING base64_decode() expects parameter 1 to be string, array given +# - in /var/www/html/lib/api/xmlrpc/v1/xmlrpc.class.php - Line 5834 +# +# newAttachment = myTestLink.uploadExecutionAttachment(NEWATTACHMENT_PY, newResultID_B, +# 'Textfile Example', 'Attachment Example for a TC Execution and TP delete test', +# filename='MyPyTPDeleteTest.py') +# print("uploadExecutionAttachment", newAttachment) response = myTestLink.getTotalsForTestPlan(newTestPlanID_C) print("getTotalsForTestPlan before delete", response) response = myTestLink.deleteTestPlan(newTestPlanID_C) @@ -545,6 +603,18 @@ except TLResponseError as tl_err: print(tl_err.message) +# -- Create Build D and copy Testers from Build A +newBuild = myTestLink.createBuild(newTestPlanID_A, NEWBUILD_D, + 'Build with copied testers from Build ' + NEWBUILD_A, + active=1, open=1, copytestersfrombuild=newBuildID_A) +print("createBuild", newBuild) +newBuildID_D = newBuild[0]['id'] +print("New Build '%s' - id: %s" % (NEWBUILD_D, newBuildID_D)) + +# close build A - buildid must be converted to an integer +response = myTestLink.closeBuild( int(newBuildID_A) ) +print("closeBuild", response) + # get information - TestProject response = myTestLink.getTestProjectByName(NEWPROJECT) print("getTestProjectByName", response) @@ -590,27 +660,59 @@ response = myTestLink.getTestCasesForTestSuite(newTestSuiteID_B, False, 'only_id') print("getTestCasesForTestSuite B", response) +# Update test suite B details - Using Project ID +updatedTestSuite = myTestLink.updateTestSuite(newTestSuiteID_B, + testprojectid=newProjectID, + details="updated Details of the Test Suite B") +print("updateTestSuite", updatedTestSuite) + +# Update test suite A name and order details - Using Project Name +# with TL 1.9.15 this step fails - solution see TL Mantis Ticket 7696 +# +changedNEWTESTSUITE_A = NEWTESTSUITE_A + ' - Changed' +updatedTestSuite = myTestLink.updateTestSuite(newTestSuiteID_A, prefix=NEWPREFIX, + testsuitename = changedNEWTESTSUITE_A, order=1) +print("updateTestSuite", updatedTestSuite) + +# get all test suites, using the same name - test Suite B +response = myTestLink.getTestSuite(NEWTESTSUITE_B, NEWPREFIX) +print("getTestSuite", response) + # get informationen - TestCase # -- Start CHANGE v0.4.5 -- #response = myTestLink.getTestCaseIDByName(NEWTESTCASE_B, None, NEWPROJECT) response = myTestLink.getTestCaseIDByName(NEWTESTCASE_B, testprojectname=NEWPROJECT) # -- END CHANGE v0.4.5 -- print("getTestCaseIDByName", response) -tcpathname = '::'.join([NEWPROJECT, NEWTESTSUITE_A, NEWTESTSUITE_AA, NEWTESTCASE_AA]) +tcpathname = '::'.join([NEWPROJECT, changedNEWTESTSUITE_A, NEWTESTSUITE_AA, NEWTESTCASE_AA]) response = myTestLink.getTestCaseIDByName('unknown', testcasepathname=tcpathname) print("getTestCaseIDByName", response) # get execution result response = myTestLink.getLastExecutionResult(newTestPlanID_A, None, testcaseexternalid=tc_aa_full_ext_id) print("getLastExecutionResult", response) +response = myTestLink.getExecutionSet(newTestPlanID_A, + testcaseexternalid=tc_aa_full_ext_id) +print("getExecutionSet", response) response = myTestLink.getLastExecutionResult(newTestPlanID_A, newTestCaseID_B) print("getLastExecutionResult", response) -if not myTLVersion == '<= 1.9.8': - # new optional arguments platformid , buildid with TL 1.9.9 - response = myTestLink.getLastExecutionResult( - newTestPlanID_A, newTestCaseID_AA, - platformid=newPlatFormID_A) - print("getLastExecutionResult", response) +response = myTestLink.getExecutionSet(newTestPlanID_A, + testcaseid=newTestCaseID_B) +print("getExecutionSet", response) +response = myTestLink.getLastExecutionResult(newTestPlanID_A, newTestCaseID_AA, + platformid=newPlatFormID_A) +print("getLastExecutionResult", response) +response = myTestLink.getExecutionSet(newTestPlanID_A, + testcaseid=newTestCaseID_AA, platformid=newPlatFormID_A) +print("getExecutionSet", response) + +response = myTestLink.getAllExecutionsResults(newTestPlanID_A, + testcaseid=newTestCaseID_AA, options={'getBugs' : 1}) +print("getAllExecutionsResults", response) +response = myTestLink.getAllExecutionsResults(newTestPlanID_B, + testcaseid=newTestCaseID_B, options={'getBugs' : 1}) +print("getAllExecutionsResults", response) + response = myTestLink.getExecCountersByBuild(newTestPlanID_A) print("getExecCountersByBuild", response) @@ -635,22 +737,56 @@ newAttachment = myTestLink.uploadTestSuiteAttachment(NEWATTACHMENT_PNG, newTestSuiteID_A, title='PNG Example', description='PNG Attachment Example for a TestSuite') print("uploadTestSuiteAttachment", newAttachment) -# add png file as Attachment to test case B -a_file=open(NEWATTACHMENT_PNG, mode='rb') -newAttachment = myTestLink.uploadTestCaseAttachment(a_file, newTestCaseID_B, - title='PNG Example', description='PNG Attachment Example for a TestCase') -print("uploadTestCaseAttachment", newAttachment) -# get Attachment of test case B -# response = myTestLink.getTestCaseAttachments(testcaseexternalid=tc_aa_full_ext_id) -# print "getTestCaseAttachments", response -response = myTestLink.getTestCaseAttachments(testcaseid=newTestCaseID_B) -print("getTestCaseAttachments", response) - -# copy test cases +# get Attachment of test suite A +response = myTestLink.getTestSuiteAttachments(newTestSuiteID_A) +print("getTestSuiteAttachments", response) + +# copy test case - as a new TC version print("create new version of TC B") response = myTestLink.copyTCnewVersion(newTestCaseID_B, summary='new version of TC B', importance='1') print('copyTCnewVersion', response) + +# get the different TC versions +response = myTestLink.getTestCaseByVersion(newTestCaseID_B, 1) +print('getTestCaseByVersion v1', response) +response = myTestLink.getTestCaseByVersion(newTestCaseID_B, 2) +print('getTestCaseByVersion v2', response) +response = myTestLink.getTestCaseByVersion(newTestCaseID_B) +print('getTestCaseByVersion vNone', response) + +#FIXME: know 1.9.19 issue +# E_WARNING base64_decode() expects parameter 1 to be string, array given +# - in /var/www/html/lib/api/xmlrpc/v1/xmlrpc.class.php - Line 5103 +# +# # add png file as Attachment to test case B version 1 +# a_file=open(NEWATTACHMENT_PNG, mode='rb') +# newAttachment = myTestLink.uploadTestCaseAttachment(a_file, newTestCaseID_B, 1, +# title='PNG Example v1', description='PNG Attachment Example for a TestCase v1') +# print("uploadTestCaseAttachment v1", newAttachment) +# # add py file as Attachment to test case B version 2 +# a_file=open(NEWATTACHMENT_PY, mode='rb') +# newAttachment = myTestLink.uploadTestCaseAttachment(a_file, newTestCaseID_B, 2, +# title='Textfile Example v2', description='Text Attachment Example for a TestCase v2') +# print("uploadTestCaseAttachment v2", newAttachment) +# +# # get Attachment of test case B v1 +# # response = myTestLink.getTestCaseAttachments(testcaseexternalid=tc_aa_full_ext_id) +# # print "getTestCaseAttachments", response +# response = myTestLink.getTestCaseAttachments(testcaseid=newTestCaseID_B, version=1) +# print("getTestCaseAttachments v1", response) +# +# # get Attachment of test case B - last version +# # response = myTestLink.getTestCaseAttachments(testcaseexternalid=tc_aa_full_ext_id) +# # print "getTestCaseAttachments", response +# response = myTestLink.getTestCaseAttachments(testcaseid=newTestCaseID_B, version=2) +# attachment_id = list(response)[0] +# print("getTestCaseAttachments v2", response[attachment_id]['title']) +# response = myTestLink.getTestCaseAttachments(testcaseid=newTestCaseID_B) +# print("getTestCaseAttachments vNone", response[attachment_id]['name']) + + +# copy test case - as new TC in a different TestSuite print("copy TC B as TC BA into Test suite A") response = myTestLink.copyTCnewTestCase(newTestCaseID_B, testsuiteid=newTestSuiteID_A, testcasename='%sA' % NEWTESTCASE_B) @@ -660,23 +796,26 @@ response = myTestLink.getTestCasesForTestSuite(newTestSuiteID_A, True, 'simple') print('getTestCasesForTestSuite A', response) -# no test data -# response = myTestLink.getTestCaseCustomFieldDesignValue( -# tc_aa_full_ext_id, 1, newProjectID, 'cfieldname', 'simple') -# print "getTestCaseCustomFieldDesignValue", response -print("getTestCaseCustomFieldDesignValue", "Sorry currently no testdata") -# add png file as Attachemnt to a requirement specification. -print("uploadRequirementSpecificationAttachment", "Sorry currently no testdata") -# add png file as Attachemnt to a requirement. -print("uploadRequirementAttachment", "Sorry currently no testdata") -# add requirements to testcase AA -# response = myTestLink.assignRequirements(tc_aa_full_ext_id, newProjectID, -# [{'req_spec' : 6729, 'requirements' : [6731]}, -# {'req_spec' : 6733, 'requirements' : [6735, 6737]}]) -print("assignRequirements", "Sorry currently no testdata") +# sample, how the test plan can be updated to use the new tc version +# site effect of this step, assigned testers and existing execution results are +# not accessible anymore via the TL Web Gui. +# That is the reason, why we have uncomment it for the normal sample execution +# response = myTestLink.addTestCaseToTestPlan(newProjectID, newTestPlanID_B, +# tc_b_full_ext_id, tc_version+1, +# overwrite=1) +# print("addTestCaseToTestPlan overwrite", response) + +# sample, how to use TestGenReporter for adding test case result for test plans, +# test builds, which are not yet defined +tgr = TestGenReporter(myTestLink, [tc_aa_full_ext_id, tc_b_full_ext_id], + testprojectname=NEWPROJECT, + testplanname=NEWTESTPLAN_G, platformname=NEWPLATFORM_G, + buildname=NEWBUILD_G, status='p') +tgr.report() +print("TestPlan, Build, Platform generated with reporting TC results", tgr ) print("") diff --git a/example/TestLinkExampleGenericApi.py b/example/TestLinkExampleGenericApi.py index f7a4c53..1618ff2 100644 --- a/example/TestLinkExampleGenericApi.py +++ b/example/TestLinkExampleGenericApi.py @@ -1,683 +1,808 @@ -#! /usr/bin/python -# -*- coding: UTF-8 -*- - -# Copyright 2013-2015 Luiko Czub, TestLink-API-Python-client developers -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# ------------------------------------------------------------------------ - - -""" - -Shows how to use the TestLinkAPIGeneric. -- does equal things as Example TestLinkAPI in TestLinkExample.py - - exception - this test project uses platforms - -=> Counts and lists the Projects -=> Create a new Project with the following structure: - - -NewProject - | - ----NewTestPlan - | - ------ Test Suite A - | | - | ------- Test Suite AA - | | - | --------- Test Case AA - | | - ------ Test Suite B --- 5 manual test steps - | - --------- Test Case B - | - --- 5 automated test steps -""" -from __future__ import print_function -from testlink import TestlinkAPIGeneric, TestLinkHelper -from testlink.testlinkerrors import TLResponseError -import sys, os.path -from platform import python_version - -# precondition a) -# SERVER_URL and KEY are defined in environment -# TESTLINK_API_PYTHON_SERVER_URL=http://YOURSERVER/testlink/lib/api/xmlrpc.php -# TESTLINK_API_PYTHON_DEVKEY=7ec252ab966ce88fd92c25d08635672b -# -# alternative precondition b) -# SERVEUR_URL and KEY are defined as command line arguments -# python TestLinkExample.py --server_url http://YOURSERVER/testlink/lib/api/xmlrpc.php -# --devKey 7ec252ab966ce88fd92c25d08635672b -# -# ATTENTION: With TestLink 1.9.7, cause of the new REST API, the SERVER_URL -# has changed from -# (old) http://YOURSERVER/testlink/lib/api/xmlrpc.php -# to -# (new) http://YOURSERVER/testlink/lib/api/xmlrpc/v1/xmlrpc.php -tl_helper = TestLinkHelper() -tl_helper.setParamsFromArgs('''Shows how to use the TestLinkAPI. -=> Counts and lists the Projects -=> Create a new Project with the following structure:''') -myTestLink = tl_helper.connect(TestlinkAPIGeneric) - -myPyVersion = python_version() -myPyVersionShort = myPyVersion.replace('.', '')[:2] - -NEWTESTPLAN_A="TestPlan_API_GENERIC A" -NEWTESTPLAN_B="TestPlan_API_GENERIC B" -NEWTESTPLAN_C="TestPlan_API_GENERIC C - DeleteTest" -NEWPLATFORM_A='Big Bird %s' % myPyVersionShort -NEWPLATFORM_B='Small Bird' -NEWPLATFORM_C='Ugly Bird' -NEWTESTSUITE_A="A - First Level" -NEWTESTSUITE_B="B - First Level" -NEWTESTSUITE_AA="AA - Second Level" -NEWTESTCASE_AA="TESTCASE_AA" -NEWTESTCASE_B="TESTCASE_B" -myApiVersion='%s v%s' % (myTestLink.__class__.__name__ , myTestLink.__version__) -NEWBUILD_A='%s' % myApiVersion -NEWBUILD_B='%s' % myApiVersion -NEWBUILD_C='%s - DeleteTest' % myApiVersion - -this_file_dirname=os.path.dirname(__file__) -NEWATTACHMENT_PY= os.path.join(this_file_dirname, 'TestLinkExampleGenericApi.py') -NEWATTACHMENT_PNG=os.path.join(this_file_dirname, 'PyGreat.png') - -# Servers TestLink Version -myTLVersion = myTestLink.testLinkVersion() -myTLVersionShort = myTLVersion.replace('.', '') - -NEWPROJECT="PROJECT_API_GENERIC-%s" % myPyVersionShort -NEWPREFIX="GPROAPI%s" % myPyVersionShort - -# used connection settings -print(myTestLink.connectionInfo()) -print("") - -# CHANGE this name into a valid account, known in your TL application -myTestUserName="pyTLapi" -myTestUserName2="admin" -# get user information -response = myTestLink.getUserByLogin(myTestUserName) -print("getUserByLogin", response) -myTestUserID=response[0]['dbID'] -response = myTestLink.getUserByID(myTestUserID) -print("getUserByID ", response) - -# example asking the api client about methods arguments -print(myTestLink.whatArgs('createTestCase')) - - -# example handling Response Error Codes -# first check an invalid devKey and than the own one -try: - myTestLink.checkDevKey(devKey='007') -except TLResponseError as tl_err: - if tl_err.code == 2000: - # expected invalid devKey Error - # now check the own one - just call with default settings - myTestLink.checkDevKey() - else: - # seems to be another response failure - we forward it - raise - -print("Number of Projects in TestLink: %i " % len(myTestLink.getProjects())) -print("") -for project in myTestLink.getProjects(): - print("Name: %(name)s ID: %(id)s " % project) -print("") - -# Delete the project, if it already exists -try: - response = myTestLink.deleteTestProject(NEWPREFIX) - print("deleteTestProject", response) -except TLResponseError: - print("No project with prefix %s exists" % NEWPREFIX) - -# Creates the project -projInfo = 'Example created with Python %s API class %s in TL %s' % \ - ( myPyVersion, myApiVersion, myTLVersion ) -newProject = myTestLink.createTestProject(NEWPROJECT, NEWPREFIX, - notes=projInfo, active=1, public=1, - options={'requirementsEnabled' : 1, 'testPriorityEnabled' : 1, - 'automationEnabled' : 1, 'inventoryEnabled' : 1}) -print("createTestProject", newProject) -newProjectID = newProject[0]['id'] -print("New Project '%s' - id: %s" % (NEWPROJECT,newProjectID)) - -# Create test plan A - uses platforms -newTestPlan = myTestLink.createTestPlan(NEWTESTPLAN_A, testprojectname=NEWPROJECT, - notes='New TestPlan created with the Generic API - uses platforms.', - active=1, public=1) -print("createTestPlan", newTestPlan) -newTestPlanID_A = newTestPlan[0]['id'] -print("New Test Plan '%s' - id: %s" % (NEWTESTPLAN_A,newTestPlanID_A)) - -# Create test plan B - uses no platforms -newTestPlan = myTestLink.createTestPlan(NEWTESTPLAN_B, prefix=NEWPREFIX, - notes='New TestPlan created with the Generic API - uses no platforms.', - active=1, public=1) -print("createTestPlan", newTestPlan) -newTestPlanID_B = newTestPlan[0]['id'] -print("New Test Plan '%s' - id: %s" % (NEWTESTPLAN_B,newTestPlanID_B)) - -# Create platform 'Big Bird x' -newPlatForm = myTestLink.createPlatform(NEWPROJECT, NEWPLATFORM_A, - notes='Platform for Big Birds, unique name, only used in this project') -print("createPlatform", newPlatForm) -newPlatFormID_A = newPlatForm['id'] -# Add Platform 'Big Bird x' to platform -response = myTestLink.addPlatformToTestPlan(newTestPlanID_A, NEWPLATFORM_A) -print("addPlatformToTestPlan", response) - -# Create platform 'Small Bird' -newPlatForm = myTestLink.createPlatform(NEWPROJECT, NEWPLATFORM_B, - notes='Platform for Small Birds, name used in all example projects') -print("createPlatform", newPlatForm) -newPlatFormID_B = newPlatForm['id'] -# Add Platform 'Small Bird' to platform -response = myTestLink.addPlatformToTestPlan(newTestPlanID_A, NEWPLATFORM_B) -print("addPlatformToTestPlan", response) - -# Create platform 'Ugly Bird' -newPlatForm = myTestLink.createPlatform(NEWPROJECT, NEWPLATFORM_C, - notes='Platform for Ugly Birds, will be removed from test plan') -print("createPlatform", newPlatForm) -newPlatFormID_C = newPlatForm['id'] -# Add Platform 'Ugly Bird' to platform -response = myTestLink.addPlatformToTestPlan(newTestPlanID_A, NEWPLATFORM_C) -print("addPlatformToTestPlan", response) - -#Creates the test Suite A -newTestSuite = myTestLink.createTestSuite(newProjectID, NEWTESTSUITE_A, - "Details of the Test Suite A") -print("createTestSuite", newTestSuite) -newTestSuiteID_A = newTestSuite[0]['id'] -print("New Test Suite '%s' - id: %s" % (NEWTESTSUITE_A, newTestSuiteID_A)) - -FirstLevelID = newTestSuiteID_A - -#Creates the test Suite B -newTestSuite = myTestLink.createTestSuite(newProjectID, NEWTESTSUITE_B, - "Details of the Test Suite B") -print("createTestSuite", newTestSuite) -newTestSuiteID_B = newTestSuite[0]['id'] -print("New Test Suite '%s' - id: %s" % (NEWTESTSUITE_B, newTestSuiteID_B)) - -#Creates the test Suite AA -newTestSuite = myTestLink.createTestSuite(newProjectID, NEWTESTSUITE_AA, - "Details of the Test Suite AA",parentid=FirstLevelID) -print("createTestSuite", newTestSuite) -newTestSuiteID_AA = newTestSuite[0]['id'] -print("New Test Suite '%s' - id: %s" % (NEWTESTSUITE_AA, newTestSuiteID_AA)) - -MANUAL = 1 -AUTOMATED = 2 -# -# #Creates the test case TC_AA -steps_tc_aa = [ - {'step_number' : 1, 'actions' : "Step action 1 - aa" , - 'expected_results' : "Step result 1 - aa", 'execution_type' : MANUAL}, - {'step_number' : 2, 'actions' : "Step action 2 - aa" , - 'expected_results' : "Step result 2 - aa", 'execution_type' : MANUAL}, - {'step_number' : 3, 'actions' : "Step action 3 - aa" , - 'expected_results' : "Step result 3 - aa", 'execution_type' : MANUAL}, - {'step_number' : 4, 'actions' : "Step action 4 - aa" , - 'expected_results' : "Step result 4 - aa", 'execution_type' : MANUAL}, - {'step_number' : 5, 'actions' : "Step action 5 - aa" , - 'expected_results' : "Step result 5 - aa", 'execution_type' : MANUAL}, - {'step_number' : 8, 'actions' : "Dummy step for delete tests" , - 'expected_results' : "should be delete with deleteTestCaseSteps", - 'execution_type' : MANUAL} - ] -newTestCase = myTestLink.createTestCase(NEWTESTCASE_AA, newTestSuiteID_AA, - newProjectID, myTestUserName, "This is the summary of the Test Case AA", - steps_tc_aa, preconditions='these are the preconditions') -print("createTestCase", newTestCase) -newTestCaseID_AA = newTestCase[0]['id'] -print("New Test Case '%s' - id: %s" % (NEWTESTCASE_AA, newTestCaseID_AA)) - -#Creates the test case TC_B -steps_tc_b = [ - {'step_number' : 1, 'actions' : "Step action 1 -b " , - 'expected_results' : "Step result 1 - b", 'execution_type' : AUTOMATED}, - {'step_number' : 2, 'actions' : "Step action 2 -b " , - 'expected_results' : "Step result 2 - b", 'execution_type' : AUTOMATED}, - {'step_number' : 3, 'actions' : "Step action 3 -b " , - 'expected_results' : "Step result 3 - b", 'execution_type' : AUTOMATED}, - {'step_number' : 4, 'actions' : "Step action 4 -b " , - 'expected_results' : "Step result 4 - b", 'execution_type' : AUTOMATED}, - {'step_number' : 5, 'actions' : "Step action 5 -b " , - 'expected_results' : "Step result 5 - b", 'execution_type' : AUTOMATED}] - -newTestCase = myTestLink.createTestCase(NEWTESTCASE_B, newTestSuiteID_B, - newProjectID, myTestUserName, "This is the summary of the Test Case B", - steps_tc_b, preconditions='these are the preconditions', - executiontype=AUTOMATED) -print("createTestCase", newTestCase) -newTestCaseID_B = newTestCase[0]['id'] -print("New Test Case '%s' - id: %s" % (NEWTESTCASE_B, newTestCaseID_B)) - -# Add test cases to test plan - we need the full external id ! -# for every test case version 1 is used -tc_version=1 -# TC AA should be tested with platforms 'Big Bird'+'Small Bird' -response = myTestLink.getTestCase(testcaseid=newTestCaseID_AA) -print("getTestCase", response) -tc_aa_full_ext_id = response[0]['full_tc_external_id'] -response = myTestLink.addTestCaseToTestPlan(newProjectID, newTestPlanID_A, - tc_aa_full_ext_id, tc_version, platformid=newPlatFormID_A) -print("addTestCaseToTestPlan", response) -tc_aa_full_ext_id = myTestLink.getTestCase(testcaseid=newTestCaseID_AA)[0]['full_tc_external_id'] -response = myTestLink.addTestCaseToTestPlan(newProjectID, newTestPlanID_A, - tc_aa_full_ext_id, tc_version, platformid=newPlatFormID_B) -print("addTestCaseToTestPlan", response) -# change test case TC_AA - delete step 8 (step 7 does not exist) -response = myTestLink.deleteTestCaseSteps(tc_aa_full_ext_id, [7,8], - version=tc_version) -print("deleteTestCaseSteps", response) - -# TC B should be tested with platform 'Small Bird' -tc_b_full_ext_id = myTestLink.getTestCase(testcaseid=newTestCaseID_B)[0]['full_tc_external_id'] -response = myTestLink.addTestCaseToTestPlan(newProjectID, newTestPlanID_A, - tc_b_full_ext_id, tc_version, platformid=newPlatFormID_B) -print("addTestCaseToTestPlan", response) - -#Update test case TC_B -> high, change step 5, new step 6 -steps_tc_b_v1u = steps_tc_b[:4] -steps_tc_b_v1u.append( - {'step_number' : 5, 'actions' : "Step action 5 -b changed by updateTestCase" , - 'expected_results' : "Step result 5 - b changed", 'execution_type' : AUTOMATED}) -steps_tc_b_v1u.append( - {'step_number' : 6, 'actions' : "Step action 6 -b added by updateTestCase" , - 'expected_results' : "Step result 6 - b added", 'execution_type' : AUTOMATED}) -response = myTestLink.updateTestCase(tc_b_full_ext_id, version=tc_version, - steps=steps_tc_b_v1u, importance='high', estimatedexecduration=3) -print("updateTestCase", response) - -# create additional steps via createTestCaseSteps - action create -steps_tc_b_c67 = [ - {'step_number' : 6, 'actions' : "action 6 createTestCaseSteps.create" , - 'expected_results' : "skip - cause step 6 already exist", 'execution_type' : AUTOMATED}, - {'step_number' : 7, 'actions' : "action 7 createTestCaseSteps.create" , - 'expected_results' : "create - cause step 7 not yet exist", 'execution_type' : AUTOMATED}] -response = myTestLink.createTestCaseSteps('create', steps_tc_b_c67, - testcaseexternalid=tc_b_full_ext_id, version=tc_version) -print("createTestCaseSteps.create", response) -# create additional steps via createTestCaseSteps - action update -steps_tc_b_c38 = [ - {'step_number' : 3, 'actions' : "action 3 createTestCaseSteps.update" , - 'expected_results' : "update - cause step 3 already exist", 'execution_type' : AUTOMATED}, - {'step_number' : 8, 'actions' : "action 8 createTestCaseSteps.update" , - 'expected_results' : "create - cause step 8 not yet exist", 'execution_type' : AUTOMATED}] -response = myTestLink.createTestCaseSteps('update', steps_tc_b_c38, - testcaseid=newTestCaseID_B, version=tc_version) -print("createTestCaseSteps.update", response) - - -# In test plan B TC B should be tested without platform -response = myTestLink.addTestCaseToTestPlan(newProjectID, newTestPlanID_B, - tc_b_full_ext_id, tc_version) -print("addTestCaseToTestPlan", response) - -# # Try to Remove Platform 'Big Bird' from platform -# response = myTestLink.removePlatformFromTestPlan(newTestPlanID_A, NEWPLATFORM_C) -# print "removePlatformFromTestPlan", response - -# Remove Platform 'Ugly Bird' from platform -response = myTestLink.removePlatformFromTestPlan(newTestPlanID_A, NEWPLATFORM_C) -print("removePlatformFromTestPlan", response) - - -# -- Create Build for TestPlan A (uses platforms) -newBuild = myTestLink.createBuild(newTestPlanID_A, NEWBUILD_A, - buildnotes='Build for TestPlan with platforms') -print("createBuild", newBuild) -newBuildID_A = newBuild[0]['id'] -print("New Build '%s' - id: %s" % (NEWBUILD_A, newBuildID_A)) - -# assign user to test case execution tasks - test plan with platforms -response = myTestLink.assignTestCaseExecutionTask( myTestUserName, - newTestPlanID_A, tc_aa_full_ext_id, - buildid=newBuildID_A, platformname=NEWPLATFORM_A) -print("assignTestCaseExecutionTask", response) -response = myTestLink.assignTestCaseExecutionTask( myTestUserName2, - newTestPlanID_A, tc_aa_full_ext_id, - buildname=NEWBUILD_A, platformid=newPlatFormID_B) -print("assignTestCaseExecutionTask", response) -response = myTestLink.assignTestCaseExecutionTask( myTestUserName, - newTestPlanID_A, tc_b_full_ext_id, - buildname=NEWBUILD_A, platformname=NEWPLATFORM_B) -print("assignTestCaseExecutionTask", response) - -# get test case assigned tester -response = myTestLink.getTestCaseAssignedTester( - newTestPlanID_A, tc_aa_full_ext_id, - buildid=newBuildID_A, platformname=NEWPLATFORM_A) -print("getTestCaseAssignedTester TC_AA TP_A Platform A", response) -response = myTestLink.getTestCaseAssignedTester( - newTestPlanID_A, tc_aa_full_ext_id, - buildname=NEWBUILD_A, platformid=newPlatFormID_B) -print("getTestCaseAssignedTester TC_AA TP_A Platform B", response) -response = myTestLink.getTestCaseAssignedTester( - newTestPlanID_A, tc_b_full_ext_id, - buildname=NEWBUILD_A, platformname=NEWPLATFORM_B) -print("getTestCaseAssignedTester TC_B TP_A Platform B", response) - -# get bugs for test case TC_AA in test plan A - state TC not executed -response = myTestLink.getTestCaseBugs(newTestPlanID_A, - testcaseexternalid=tc_aa_full_ext_id) -print("getTestCaseBugs TC_AA in TP_A (TC is not executed)", response) - -# report Test Case Results for platform 'Big Bird' -# TC_AA failed, build should be guessed, TC identified with external id -newResult = myTestLink.reportTCResult(newTestPlanID_A, 'f', guess=True, - testcaseexternalid=tc_aa_full_ext_id, - platformname=NEWPLATFORM_A, - execduration=2.9, timestamp='2014-09-18 14:33') -print("reportTCResult", newResult) -newResultID_AA = newResult[0]['id'] - -# get bugs for test case TC_AA in test plan A - state TC is executed -response = myTestLink.getTestCaseBugs(newTestPlanID_A, - testcaseexternalid=tc_aa_full_ext_id) -print("getTestCaseBugs TC_AA in TP_A (TC is executed, no bug)", response) - -# report Test Case Results for platform 'Small Bird' -# TC_AA passed, build should be guessed, TC identified with external id -newResult = myTestLink.reportTCResult(newTestPlanID_A, 'p', guess=True, - testcaseexternalid=tc_aa_full_ext_id, - platformid=newPlatFormID_B, - execduration='3.2', timestamp='2014-09-19 14:33:02') -print("reportTCResult", newResult) -newResultID_AA_p = newResult[0]['id'] -# TC_B passed, explicit build and some notes , TC identified with internal id -newResult = myTestLink.reportTCResult(newTestPlanID_A, 'p', - buildid=newBuildID_A, testcaseid=newTestCaseID_B, - platformname=NEWPLATFORM_B, notes="first try") -print("reportTCResult", newResult) -newResultID_B = newResult[0]['id'] - -# add this python file as Attachemnt to last execution of TC_B with -# different filename 'MyPyExampleApiGeneric.py' -a_file=open(NEWATTACHMENT_PY) -newAttachment = myTestLink.uploadExecutionAttachment(a_file, newResultID_B, - title='Textfile Example', description='Text Attachment Example for a TestCase Execution', - filename='MyPyExampleApiGeneric.py') -print("uploadExecutionAttachment", newAttachment) -# add png file as Attachemnt to last execution of TC_AA -# !Attention - on WINDOWS use binary mode for none text file -# see http://docs.python.org/2/tutorial/inputoutput.html#reading-and-writing-files -a_file=open(NEWATTACHMENT_PNG, mode='rb') -newAttachment = myTestLink.uploadExecutionAttachment(a_file, newResultID_AA, - title='PNG Example', description='PNG Attachment Example for a TestCase Execution') -print("uploadExecutionAttachment", newAttachment) - -# -- Create Build for TestPlan B (uses no platforms) -newBuild = myTestLink.createBuild(newTestPlanID_B, NEWBUILD_B, - buildnotes='Build for TestPlan without platforms') -print("createBuild", newBuild) -newBuildID_B = newBuild[0]['id'] -print("New Build '%s' - id: %s" % (NEWBUILD_B, newBuildID_B)) - -# assign user to test case execution tasks - test plans without platforms -response = myTestLink.assignTestCaseExecutionTask( myTestUserName, - newTestPlanID_B, tc_b_full_ext_id, buildname=NEWBUILD_B) -print("assignTestCaseExecutionTask", response) - -# get test case assigned tester -response = myTestLink.getTestCaseAssignedTester( - newTestPlanID_B, tc_b_full_ext_id, buildname=NEWBUILD_B) -print("getTestCaseAssignedTester TC_B TP_B no Platform", response) - -# try to remove not assigned tester -response = myTestLink.unassignTestCaseExecutionTask( - newTestPlanID_B, tc_b_full_ext_id, buildname=NEWBUILD_B, - user=myTestUserName2) -print("unassignTestCaseExecutionTask not assigned user", response) -response = myTestLink.getTestCaseAssignedTester( - newTestPlanID_B, tc_b_full_ext_id, buildname=NEWBUILD_B) -print("getTestCaseAssignedTester TC_B TP_B no Platform", response) - -# try to remove all assigned tester -response = myTestLink.unassignTestCaseExecutionTask( - newTestPlanID_B, tc_b_full_ext_id, buildid=newBuildID_B, - action='unassignAll') -print("unassignTestCaseExecutionTask unassignAll", response) -response = myTestLink.getTestCaseAssignedTester( - newTestPlanID_B, tc_b_full_ext_id, buildname=NEWBUILD_B) -print("getTestCaseAssignedTester TC_B TP_B no Platform", response) - -# reassign user to test case execution tasks - test plans without platforms -response = myTestLink.assignTestCaseExecutionTask( myTestUserName, - newTestPlanID_B, tc_b_full_ext_id, buildid=newBuildID_B) -print("assignTestCaseExecutionTask", response) -response = myTestLink.getTestCaseAssignedTester( - newTestPlanID_B, tc_b_full_ext_id, buildname=NEWBUILD_B) -print("getTestCaseAssignedTester TC_B TP_B no Platform", response) - -# TC_B in test plan b (without platform) -# first try failed (with bug), second blocked - all by user myTestUserName -newResult = myTestLink.reportTCResult(newTestPlanID_B, 'f', - buildid=newBuildID_B, testcaseid=newTestCaseID_B, bugid='007', - notes="no birds are singing", user=myTestUserName) -print("reportTCResult", newResult) -newResultID_B_f = newResult[0]['id'] -newResult = myTestLink.reportTCResult(newTestPlanID_B, 'b', - buildid=newBuildID_B, testcaseid=newTestCaseID_B, bugid='008', - notes="hungry birds blocks the execution", user=myTestUserName) -print("reportTCResult", newResult) -newResultID_B_b = newResult[0]['id'] -# get bugs for test case TC_B in test plan B - state TC is executed with bug -response = myTestLink.getTestCaseBugs(newTestPlanID_B, - testcaseid=newTestCaseID_B) -print("getTestCaseBugs TC_B in TP_B (TC is executed with 2 bugs)", response) - - -# now we make a mistake and commit the same result a second time -# and try to delete this mistake -newResult = myTestLink.reportTCResult(newTestPlanID_B, 'b', - buildid=newBuildID_B, testcaseid=newTestCaseID_B, - notes="mistake, commit same result a second time") -print("reportTCResult", newResult) -newResultID_B_b2 = int(newResult[0]['id']) -try: - # if TL configuration allows deletion of executions, no error will occur - response = myTestLink.deleteExecution(newResultID_B_b2) - print("deleteExecution", response) -except TLResponseError as tl_err: - if tl_err.code == 232: - # TL configuration does not allow deletion of executions - pass - else: - # sh..: another problem occurs - raise - -# now we try to change the execution types of the test cases -# - AA from manual -> auto and B from auto -> manual -newResult = myTestLink.setTestCaseExecutionType(tc_aa_full_ext_id, tc_version, - newProjectID, AUTOMATED) -print("setTestCaseExecutionType", response) -newResult = myTestLink.setTestCaseExecutionType(tc_b_full_ext_id, tc_version, - newProjectID, MANUAL) -print("setTestCaseExecutionType", response) - -# create TestPlan C with Platform, Build , TestCase, assigned TestCase -# and delete it -newTestPlan = myTestLink.createTestPlan(NEWTESTPLAN_C, NEWPROJECT, - notes='TestPlan for delete test.', - active=1, public=1) -print("createTestPlan for DeleteTest", newTestPlan) -newTestPlanID_C = newTestPlan[0]['id'] -print("Test Plan '%s' - id: %s" % (NEWTESTPLAN_C,newTestPlanID_C)) -newBuild = myTestLink.createBuild(newTestPlanID_C, NEWBUILD_C, - buildnotes='Build for TestPlan delete test') -print("createBuild for DeleteTest", newBuild) -newBuildID_C = newBuild[0]['id'] -print("Build '%s' - id: %s" % (NEWBUILD_C, newBuildID_C)) -response = myTestLink.addPlatformToTestPlan(newTestPlanID_C, NEWPLATFORM_C) -print("addPlatformToTestPlan", response) -response = myTestLink.addTestCaseToTestPlan(newProjectID, newTestPlanID_C, - tc_aa_full_ext_id, tc_version, platformid=newPlatFormID_C) -print("addTestCaseToTestPlan", response) -response = myTestLink.assignTestCaseExecutionTask( myTestUserName, - newTestPlanID_C, tc_aa_full_ext_id, buildid=newBuildID_C, - platformid=newPlatFormID_C) -print("assignTestCaseExecutionTask", response) -newResult = myTestLink.reportTCResult(newTestPlanID_C, 'p', - buildid=newBuildID_C, testcaseid=newTestCaseID_AA, - platformname=NEWPLATFORM_C, notes="TP delete test") -print("reportTCResult", newResult) -newResultID_B = newResult[0]['id'] -newAttachment = myTestLink.uploadExecutionAttachment(NEWATTACHMENT_PY, newResultID_B, - title='Textfile Example', filename='MyPyTPDeleteTest.py', - description='Attachment Example for a TC Execution and TP delete test') -print("uploadExecutionAttachment", newAttachment) -response = myTestLink.getTotalsForTestPlan(newTestPlanID_C) -print("getTotalsForTestPlan before delete", response) -response = myTestLink.deleteTestPlan(newTestPlanID_C) -print("deleteTestPlan", response) -try: - response = myTestLink.getTotalsForTestPlan(newTestPlanID_C) - print("getTotalsForTestPlan after delete", response) -except TLResponseError as tl_err: - print(tl_err.message) - -# get information - TestProject -response = myTestLink.getTestProjectByName(NEWPROJECT) -print("getTestProjectByName", response) -response = myTestLink.getProjectTestPlans(newProjectID) -print("getProjectTestPlans", response) -response = myTestLink.getFirstLevelTestSuitesForTestProject(newProjectID) -print("getFirstLevelTestSuitesForTestProject", response) -response = myTestLink.getProjectPlatforms(newProjectID) -print("getProjectPlatforms", response) -response = myTestLink.getProjectKeywords(newProjectID) -print("getProjectKeywords", response) - -# get information - TestPlan -response = myTestLink.getTestPlanByName(NEWPROJECT, NEWTESTPLAN_A) -print("getTestPlanByName", response) -response = myTestLink.getTotalsForTestPlan(newTestPlanID_A) -print("getTotalsForTestPlan", response) -response = myTestLink.getBuildsForTestPlan(newTestPlanID_A) -print("getBuildsForTestPlan", response) -response = myTestLink.getLatestBuildForTestPlan(newTestPlanID_A) -print("getLatestBuildForTestPlan", response) -response = myTestLink.getTestPlanPlatforms(newTestPlanID_A) -print("getTestPlanPlatforms", response) -response = myTestLink.getTestSuitesForTestPlan(newTestPlanID_A) -print("getTestSuitesForTestPlan", response) -# get failed Testcases -response = myTestLink.getTestCasesForTestPlan(newTestPlanID_A, executestatus='f') -print("getTestCasesForTestPlan A failed ", response) -# get Testcases for Plattform SmallBird -response = myTestLink.getTestCasesForTestPlan(newTestPlanID_A, platformid=newPlatFormID_B) -print("getTestCasesForTestPlan A SmallBirds", response) - -# get information - TestSuite -response = myTestLink.getTestSuiteByID(newTestSuiteID_B) -print("getTestSuiteByID", response) -response = myTestLink.getTestSuitesForTestSuite(newTestSuiteID_A) -print("getTestSuitesForTestSuite", response) -response = myTestLink.getTestCasesForTestSuite(newTestSuiteID_A, - deep=True, details='full') -print("getTestCasesForTestSuite", response) -response = myTestLink.getTestCasesForTestSuite(newTestSuiteID_B, - deep=False, details='only_id') -print("getTestCasesForTestSuite", response) - -# get informationen - TestCase_B -response = myTestLink.getTestCaseIDByName(NEWTESTCASE_B, testprojectname=NEWPROJECT) -print("getTestCaseIDByName", response) -# get informationen - TestCase_AA via Pathname -tcpathname = '::'.join([NEWPROJECT, NEWTESTSUITE_A, NEWTESTSUITE_AA, NEWTESTCASE_AA]) -response = myTestLink.getTestCaseIDByName('unknown', testcasepathname=tcpathname) -print("getTestCaseIDByName", response) -# get execution result -response = myTestLink.getLastExecutionResult(newTestPlanID_A, - testcaseexternalid=tc_aa_full_ext_id) -print("getLastExecutionResult", response) -response = myTestLink.getLastExecutionResult(newTestPlanID_A, - testcaseid=newTestCaseID_B) -print("getLastExecutionResult", response) -if not myTLVersion == '<= 1.9.8': - # new optional arguments platformid , buildid with TL 1.9.9 - response = myTestLink.getLastExecutionResult( - newTestPlanID_A, testcaseid=newTestCaseID_AA, - platformid=newPlatFormID_A) - print("getLastExecutionResult", response) - -response = myTestLink.getExecCountersByBuild(newTestPlanID_A) -print("getExecCountersByBuild", response) -response = myTestLink.getExecCountersByBuild(newTestPlanID_B) -print("getExecCountersByBuild", response) -response = myTestLink.getTestCaseKeywords(testcaseexternalid=tc_b_full_ext_id) -print("getTestCaseKeywords noKeyWords", response) - -# get information - general -response = myTestLink.getFullPath(int(newTestSuiteID_AA)) -print("getFullPath", response) -response = myTestLink.getFullPath([int(newTestCaseID_AA), int(newTestCaseID_B)]) -print("getFullPath", response) - -# attachments -# add png file as Attachment to test project -a_file=open(NEWATTACHMENT_PNG, mode='rb') -newAttachment = myTestLink.uploadTestProjectAttachment(a_file, newProjectID, - title='PNG Example', description='PNG Attachment Example for a TestProject') -print("uploadTestProjectAttachment", newAttachment) -# add png file as Attachnent to test suite A - uploadXyzAttachmemt also file path -newAttachment = myTestLink.uploadTestSuiteAttachment(NEWATTACHMENT_PNG, newTestSuiteID_A, - title='PNG Example', description='PNG Attachment Example for a TestSuite') -print("uploadTestSuiteAttachment", newAttachment) -# add png file as Attachment to test case B -a_file=open(NEWATTACHMENT_PNG, mode='rb') -newAttachment = myTestLink.uploadTestCaseAttachment(a_file, newTestCaseID_B, - title='PNG Example', description='PNG Attachment Example for a TestCase') -print("uploadTestCaseAttachment", newAttachment) -# get Attachment of test case B -# response = myTestLink.getTestCaseAttachments(testcaseexternalid=tc_aa_full_ext_id) -# print "getTestCaseAttachments", response -response = myTestLink.getTestCaseAttachments(testcaseid=newTestCaseID_B) -print("getTestCaseAttachments", response) - -# no test data -# response = myTestLink.getTestCaseCustomFieldDesignValue( -# tc_aa_full_ext_id, 1, newProjectID, 'cfieldname', details='simple') -# print "getTestCaseCustomFieldDesignValue", response -print("getTestCaseCustomFieldDesignValue", "Sorry currently no testdata") - -# add png file as Attachemnt to a requirement specification. -print("uploadRequirementSpecificationAttachment", "Sorry currently no testdata") -# add png file as Attachemnt to a requirement. -print("uploadRequirementAttachment", "Sorry currently no testdata") - -# add requirements to testcase AA -# response = myTestLink.assignRequirements(tc_aa_full_ext_id, newProjectID, -# [{'req_spec' : 6729, 'requirements' : [6731]}, -# {'req_spec' : 6733, 'requirements' : [6735, 6737]}]) -print("assignRequirements", "Sorry currently no testdata") - -print("") -print("Number of Projects in TestLink: %i " % len(myTestLink.getProjects())) -print("") -for project in myTestLink.getProjects(): - print("Name: %(name)s ID: %(id)s " % project) -print("") - - -# -# -# +#! /usr/bin/python +# -*- coding: UTF-8 -*- + +# Copyright 2013-2021 Luiko Czub, TestLink-API-Python-client developers +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------ + + +""" + +Shows how to use the TestLinkAPIGeneric. +- does equal things as Example TestLinkAPI in TestLinkExample.py + - exception - this test project uses platforms + +=> Counts and lists the Projects +=> Create a new Project with the following structure: + + +NewProject + | + ----NewTestPlan + | + ------ Test Suite A + | | + | ------- Test Suite AA + | | + | --------- Test Case AA + | | + ------ Test Suite B --- 5 manual test steps + | + --------- Test Case B + | + --- 5 automated test steps +""" +from __future__ import print_function +from testlink import TestlinkAPIGeneric, TestLinkHelper +from testlink.testlinkerrors import TLResponseError +import sys, os.path +from platform import python_version + +# precondition a) +# SERVER_URL and KEY are defined in environment +# TESTLINK_API_PYTHON_SERVER_URL=http://YOURSERVER/testlink/lib/api/xmlrpc.php +# TESTLINK_API_PYTHON_DEVKEY=7ec252ab966ce88fd92c25d08635672b +# +# alternative precondition b) +# SERVEUR_URL and KEY are defined as command line arguments +# python TestLinkExample.py --server_url http://YOURSERVER/testlink/lib/api/xmlrpc.php +# --devKey 7ec252ab966ce88fd92c25d08635672b +# +# ATTENTION: With TestLink 1.9.7, cause of the new REST API, the SERVER_URL +# has changed from +# (old) http://YOURSERVER/testlink/lib/api/xmlrpc.php +# to +# (new) http://YOURSERVER/testlink/lib/api/xmlrpc/v1/xmlrpc.php +tl_helper = TestLinkHelper() +tl_helper.setParamsFromArgs('''Shows how to use the TestLinkAPI. +=> Counts and lists the Projects +=> Create a new Project with the following structure:''') +myTestLink = tl_helper.connect(TestlinkAPIGeneric) + +myPyVersion = python_version() +myPyVersionShort = myPyVersion.replace('.', '')[:2] + +NEWTESTPLAN_A="TestPlan_API_GENERIC A" +NEWTESTPLAN_B="TestPlan_API_GENERIC B" +NEWTESTPLAN_C="TestPlan_API_GENERIC C - DeleteTest" +NEWPLATFORM_A='Big Bird %s' % myPyVersionShort +NEWPLATFORM_B='Small Bird' +NEWPLATFORM_C='Ugly Bird' +NEWTESTSUITE_A="A - First Level" +NEWTESTSUITE_B="B - First Level" +NEWTESTSUITE_AA="AA - Second Level" +NEWTESTCASE_AA="TESTCASE_AA" +NEWTESTCASE_B="TESTCASE_B" +myApiVersion='%s v%s' % (myTestLink.__class__.__name__ , myTestLink.__version__) +NEWBUILD_A='%s' % myApiVersion +NEWBUILD_B='%s' % myApiVersion +NEWBUILD_C='%s - DeleteTest' % myApiVersion +NEWBUILD_D='%s - copyTestersTest' % myApiVersion + +this_file_dirname=os.path.dirname(__file__) +NEWATTACHMENT_PY= os.path.join(this_file_dirname, 'TestLinkExampleGenericApi.py') +NEWATTACHMENT_PNG=os.path.join(this_file_dirname, 'PyGreat.png') + +# Servers TestLink Version +myTLVersion = myTestLink.testLinkVersion() +myTLVersionShort = myTLVersion.replace('.', '') + +NEWPROJECT="PROJECT_API_GENERIC-%s" % myPyVersionShort +NEWPREFIX="GPROAPI%s" % myPyVersionShort +ITSNAME="myITS" + +# used connection settings +print(myTestLink.connectionInfo()) +print("") + +def checkUser(name1, name2): + """ checks if user NAME1_NAME2 exists + when not , user will be created + returns username + userid + """ + + login = "{}_{}".format(name1, name2) + mail = "{}.{}@example.com".format(name1, name2) + try: + response = myTestLink.getUserByLogin(login) + userID = response[0]['dbID'] + except TLResponseError as tl_err: + if tl_err.code == 10000: + # Cannot Find User Login - create new user + userID = myTestLink.createUser(login, name1, name2, mail) + else: + # seems to be another response failure - we forward it + raise + + return login, userID + +# ensure tester and expert users exists +myTestUserName, myTestUser1_ID=checkUser("myTester", "pyTLapi") +print("checkUser", myTestUserName, myTestUser1_ID) +myTestUserName2, myTestUser2_ID=checkUser("myExpert", "pyTLapi") +print("checkUser", myTestUserName2, myTestUser2_ID) + +# get user information +response = myTestLink.getUserByLogin(myTestUserName) +print("getUserByLogin", response) +myTestUserID=response[0]['dbID'] +response = myTestLink.getUserByID(myTestUserID) +print("getUserByID ", response) + +# example asking the api client about methods arguments +print(myTestLink.whatArgs('createTestCase')) + +# example handling Response Error Codes +# first check an invalid devKey and than the own one +try: + myTestLink.checkDevKey(devKey='007') +except TLResponseError as tl_err: + if tl_err.code == 2000: + # expected invalid devKey Error + # now check the own one - just call with default settings + myTestLink.checkDevKey() + else: + # seems to be another response failure - we forward it + raise + +print("Number of Projects in TestLink: %i " % len(myTestLink.getProjects())) +print("") +for project in myTestLink.getProjects(): + print("Name: %(name)s ID: %(id)s " % project) +print("") + +# Delete the project, if it already exists +try: + response = myTestLink.deleteTestProject(NEWPREFIX) + print("deleteTestProject", response) +except TLResponseError: + print("No project with prefix %s exists" % NEWPREFIX) + +# # get IssueTrackerSystem +# aITS=myTestLink.getIssueTrackerSystem(aITSNAME) +# print("getIssueTrackerSystem", aITS) + +# Creates the project +projInfo = 'Example created with Python %s API class %s in TL %s' % \ + ( myPyVersion, myApiVersion, myTLVersion ) +newProject = myTestLink.createTestProject(NEWPROJECT, NEWPREFIX, + notes=projInfo, active=1, public=1, +# itsname=ITSNAME, itsenabled=1, + options={'requirementsEnabled' : 1, 'testPriorityEnabled' : 1, + 'automationEnabled' : 1, 'inventoryEnabled' : 1}) +print("createTestProject", newProject) +newProjectID = newProject[0]['id'] +print("New Project '%s' - id: %s" % (NEWPROJECT,newProjectID)) + +# assign project roles to user 1 and get user information +response = myTestLink.setUserRoleOnProject(myTestUser1_ID, "tester", newProjectID) +print("setUserRoleOnProject user1 role tester", response) +response = myTestLink.getUserByID(myTestUser1_ID) +print("getUserByID user1", response) + +# assign project roles to user 2 and get user information +response = myTestLink.setUserRoleOnProject(myTestUser2_ID, "senior tester", newProjectID) +print("setUserRoleOnProject user2 role senior tester", response) +response = myTestLink.getUserByID(myTestUser2_ID) +print("getUserByID user2", response) +response = myTestLink.setUserRoleOnProject(myTestUser2_ID, "test designer", newProjectID) +print("setUserRoleOnProject user2 role test designer", response) +response = myTestLink.getUserByID(myTestUser2_ID) +print("getUserByID user2", response) + + +# Create test plan A - uses platforms +newTestPlan = myTestLink.createTestPlan(NEWTESTPLAN_A, testprojectname=NEWPROJECT, + notes='New TestPlan created with the Generic API - uses platforms.', + active=1, public=1) +print("createTestPlan", newTestPlan) +newTestPlanID_A = newTestPlan[0]['id'] +print("New Test Plan '%s' - id: %s" % (NEWTESTPLAN_A,newTestPlanID_A)) + +# Create test plan B - uses no platforms +newTestPlan = myTestLink.createTestPlan(NEWTESTPLAN_B, prefix=NEWPREFIX, + notes='New TestPlan created with the Generic API - uses no platforms.', + active=1, public=1) +print("createTestPlan", newTestPlan) +newTestPlanID_B = newTestPlan[0]['id'] +print("New Test Plan '%s' - id: %s" % (NEWTESTPLAN_B,newTestPlanID_B)) + +# Create platform 'Big Bird x' +newPlatForm = myTestLink.createPlatform(NEWPROJECT, NEWPLATFORM_A, + notes='Platform for Big Birds, unique name, only used in this project', + platformondesign=True, platformonexecution=True) +print("createPlatform", newPlatForm) +newPlatFormID_A = newPlatForm['id'] +# Add Platform 'Big Bird x' to platform +response = myTestLink.addPlatformToTestPlan(newTestPlanID_A, NEWPLATFORM_A) +print("addPlatformToTestPlan", response) + +# Create platform 'Small Bird' +newPlatForm = myTestLink.createPlatform(NEWPROJECT, NEWPLATFORM_B, + notes='Platform for Small Birds, name used in all example projects', + platformondesign=True, platformonexecution=True) +print("createPlatform", newPlatForm) +newPlatFormID_B = newPlatForm['id'] +# Add Platform 'Small Bird' to platform +response = myTestLink.addPlatformToTestPlan(newTestPlanID_A, NEWPLATFORM_B) +print("addPlatformToTestPlan", response) + +# Create platform 'Ugly Bird' +newPlatForm = myTestLink.createPlatform(NEWPROJECT, NEWPLATFORM_C, + notes='Platform for Ugly Birds, will be removed from test plan', + platformondesign=True, platformonexecution=True) +print("createPlatform", newPlatForm) +newPlatFormID_C = newPlatForm['id'] +# Add Platform 'Ugly Bird' to platform +response = myTestLink.addPlatformToTestPlan(newTestPlanID_A, NEWPLATFORM_C) +print("addPlatformToTestPlan", response) + +#Creates the test Suite A +newTestSuite = myTestLink.createTestSuite(newProjectID, NEWTESTSUITE_A, + "Details of the Test Suite A") +print("createTestSuite", newTestSuite) +newTestSuiteID_A = newTestSuite[0]['id'] +print("New Test Suite '%s' - id: %s" % (NEWTESTSUITE_A, newTestSuiteID_A)) + +FirstLevelID = newTestSuiteID_A + +#Creates the test Suite B +newTestSuite = myTestLink.createTestSuite(newProjectID, NEWTESTSUITE_B, + "Details of the Test Suite B") +print("createTestSuite", newTestSuite) +newTestSuiteID_B = newTestSuite[0]['id'] +print("New Test Suite '%s' - id: %s" % (NEWTESTSUITE_B, newTestSuiteID_B)) + +#Creates the test Suite AA +newTestSuite = myTestLink.createTestSuite(newProjectID, NEWTESTSUITE_AA, + "Details of the Test Suite AA",parentid=FirstLevelID) +print("createTestSuite", newTestSuite) +newTestSuiteID_AA = newTestSuite[0]['id'] +print("New Test Suite '%s' - id: %s" % (NEWTESTSUITE_AA, newTestSuiteID_AA)) + +MANUAL = 1 +AUTOMATED = 2 +READFORREVIEW=2 +REWORK=4 +HIGH=3 +MEDIUM=2 +LOW=1 +# +# #Creates the test case TC_AA with state ready for review +steps_tc_aa = [ + {'step_number' : 1, 'actions' : "Step action 1 - aa" , + 'expected_results' : "Step result 1 - aa", 'execution_type' : MANUAL}, + {'step_number' : 2, 'actions' : "Step action 2 - aa" , + 'expected_results' : "Step result 2 - aa", 'execution_type' : MANUAL}, + {'step_number' : 3, 'actions' : "Step action 3 - aa" , + 'expected_results' : "Step result 3 - aa", 'execution_type' : MANUAL}, + {'step_number' : 4, 'actions' : "Step action 4 - aa" , + 'expected_results' : "Step result 4 - aa", 'execution_type' : MANUAL}, + {'step_number' : 5, 'actions' : "Step action 5 - aa" , + 'expected_results' : "Step result 5 - aa", 'execution_type' : MANUAL}, + {'step_number' : 8, 'actions' : "Dummy step for delete tests" , + 'expected_results' : "should be delete with deleteTestCaseSteps", + 'execution_type' : MANUAL} + ] +newTestCase = myTestLink.createTestCase(NEWTESTCASE_AA, newTestSuiteID_AA, + newProjectID, myTestUserName, "This is the summary of the Test Case AA", + steps_tc_aa, preconditions='these are the preconditions', + importance=LOW, state=READFORREVIEW, estimatedexecduration=10.1) +print("createTestCase", newTestCase) +newTestCaseID_AA = newTestCase[0]['id'] +print("New Test Case '%s' - id: %s" % (NEWTESTCASE_AA, newTestCaseID_AA)) + +# Creates the test case TC_B with state rework - in wrong test suite A +steps_tc_b = [ + {'step_number' : 1, 'actions' : "Step action 1 -b " , + 'expected_results' : "Step result 1 - b", 'execution_type' : AUTOMATED}, + {'step_number' : 2, 'actions' : "Step action 2 -b " , + 'expected_results' : "Step result 2 - b", 'execution_type' : AUTOMATED}, + {'step_number' : 3, 'actions' : "Step action 3 -b " , + 'expected_results' : "Step result 3 - b", 'execution_type' : AUTOMATED}, + {'step_number' : 4, 'actions' : "Step action 4 -b " , + 'expected_results' : "Step result 4 - b", 'execution_type' : AUTOMATED}, + {'step_number' : 5, 'actions' : "Step action 5 -b " , + 'expected_results' : "Step result 5 - b", 'execution_type' : AUTOMATED}] + +newTestCase = myTestLink.createTestCase(NEWTESTCASE_B, newTestSuiteID_A, + newProjectID, myTestUserName, "This is the summary of the Test Case B", + steps_tc_b, preconditions='these are the preconditions', + executiontype=AUTOMATED, status=REWORK, estimatedexecduration=0.5) +print("createTestCase TC-B in TS-A", newTestCase) +newTestCaseID_B = newTestCase[0]['id'] +print("New Test Case '%s' - id: %s" % (NEWTESTCASE_B, newTestCaseID_B)) + +# Move test case TC_B to correct test suite B +tc_b_full_ext_id = myTestLink.getTestCase(testcaseid=newTestCaseID_B)[0]['full_tc_external_id'] +response = myTestLink.setTestCaseTestSuite(tc_b_full_ext_id, newTestSuiteID_B) +print("setTestCaseTestSuite TC-B to TS-B" , response) + +# Add test cases to test plan - we need the full external id ! +# for every test case version 1 is used +tc_version=1 +# TC AA should be tested with platforms 'Big Bird'+'Small Bird' +response = myTestLink.getTestCase(testcaseid=newTestCaseID_AA) +print("getTestCase", response) +tc_aa_full_ext_id = response[0]['full_tc_external_id'] +response = myTestLink.addTestCaseToTestPlan(newProjectID, newTestPlanID_A, + tc_aa_full_ext_id, tc_version, platformid=newPlatFormID_A) +print("addTestCaseToTestPlan", response) +tc_aa_full_ext_id = myTestLink.getTestCase(testcaseid=newTestCaseID_AA)[0]['full_tc_external_id'] +response = myTestLink.addTestCaseToTestPlan(newProjectID, newTestPlanID_A, + tc_aa_full_ext_id, tc_version, platformid=newPlatFormID_B) +print("addTestCaseToTestPlan", response) +# change test case TC_AA - delete step 8 (step 7 does not exist) +response = myTestLink.deleteTestCaseSteps(tc_aa_full_ext_id, [7,8], + version=tc_version) +print("deleteTestCaseSteps", response) + +# TC B should be tested with platform 'Small Bird' +tc_b_full_ext_id = myTestLink.getTestCase(testcaseid=newTestCaseID_B)[0]['full_tc_external_id'] +response = myTestLink.addTestCaseToTestPlan(newProjectID, newTestPlanID_A, + tc_b_full_ext_id, tc_version, platformid=newPlatFormID_B) +print("addTestCaseToTestPlan", response) + +#Update test case TC_B -> high, change step 5, new step 6 +steps_tc_b_v1u = steps_tc_b[:4] +steps_tc_b_v1u.append( + {'step_number' : 5, 'actions' : "Step action 5 -b changed by updateTestCase" , + 'expected_results' : "Step result 5 - b changed", 'execution_type' : AUTOMATED}) +steps_tc_b_v1u.append( + {'step_number' : 6, 'actions' : "Step action 6 -b added by updateTestCase" , + 'expected_results' : "Step result 6 - b added", 'execution_type' : AUTOMATED}) +response = myTestLink.updateTestCase(tc_b_full_ext_id, version=tc_version, + steps=steps_tc_b_v1u, importance=MEDIUM, estimatedexecduration=3) +print("updateTestCase", response) + +# create additional steps via createTestCaseSteps - action create +steps_tc_b_c67 = [ + {'step_number' : 6, 'actions' : "action 6 createTestCaseSteps.create" , + 'expected_results' : "skip - cause step 6 already exist", 'execution_type' : AUTOMATED}, + {'step_number' : 7, 'actions' : "action 7 createTestCaseSteps.create" , + 'expected_results' : "create - cause step 7 not yet exist", 'execution_type' : AUTOMATED}] +response = myTestLink.createTestCaseSteps('create', steps_tc_b_c67, + testcaseexternalid=tc_b_full_ext_id, version=tc_version) +print("createTestCaseSteps.create", response) +# create additional steps via createTestCaseSteps - action update +steps_tc_b_c38 = [ + {'step_number' : 3, 'actions' : "action 3 createTestCaseSteps.update" , + 'expected_results' : "update - cause step 3 already exist", 'execution_type' : AUTOMATED}, + {'step_number' : 8, 'actions' : "action 8 createTestCaseSteps.update" , + 'expected_results' : "create - cause step 8 not yet exist", 'execution_type' : AUTOMATED}] +response = myTestLink.createTestCaseSteps('update', steps_tc_b_c38, + testcaseid=newTestCaseID_B, version=tc_version) +print("createTestCaseSteps.update", response) + + +# In test plan B TC B should be tested without platform +response = myTestLink.addTestCaseToTestPlan(newProjectID, newTestPlanID_B, + tc_b_full_ext_id, tc_version) +print("addTestCaseToTestPlan", response) + +# # Try to Remove Platform 'Big Bird' from platform +# response = myTestLink.removePlatformFromTestPlan(newTestPlanID_A, NEWPLATFORM_C) +# print "removePlatformFromTestPlan", response + +# Remove Platform 'Ugly Bird' from platform +response = myTestLink.removePlatformFromTestPlan(newTestPlanID_A, NEWPLATFORM_C) +print("removePlatformFromTestPlan", response) + + +# -- Create Build for TestPlan A (uses platforms) +newBuild = myTestLink.createBuild(newTestPlanID_A, NEWBUILD_A, + buildnotes='Build for TestPlan with platforms') +print("createBuild", newBuild) +newBuildID_A = newBuild[0]['id'] +print("New Build '%s' - id: %s" % (NEWBUILD_A, newBuildID_A)) + +# assign user to test case execution tasks - test plan with platforms +response = myTestLink.assignTestCaseExecutionTask( myTestUserName, + newTestPlanID_A, tc_aa_full_ext_id, + buildid=newBuildID_A, platformname=NEWPLATFORM_A) +print("assignTestCaseExecutionTask", response) +response = myTestLink.assignTestCaseExecutionTask( myTestUserName2, + newTestPlanID_A, tc_aa_full_ext_id, + buildname=NEWBUILD_A, platformid=newPlatFormID_B) +print("assignTestCaseExecutionTask", response) +response = myTestLink.assignTestCaseExecutionTask( myTestUserName, + newTestPlanID_A, tc_b_full_ext_id, + buildname=NEWBUILD_A, platformname=NEWPLATFORM_B) +print("assignTestCaseExecutionTask", response) + +# get test case assigned tester +response = myTestLink.getTestCaseAssignedTester( + newTestPlanID_A, tc_aa_full_ext_id, + buildid=newBuildID_A, platformname=NEWPLATFORM_A) +print("getTestCaseAssignedTester TC_AA TP_A Platform A", response) +response = myTestLink.getTestCaseAssignedTester( + newTestPlanID_A, tc_aa_full_ext_id, + buildname=NEWBUILD_A, platformid=newPlatFormID_B) +print("getTestCaseAssignedTester TC_AA TP_A Platform B", response) +response = myTestLink.getTestCaseAssignedTester( + newTestPlanID_A, tc_b_full_ext_id, + buildname=NEWBUILD_A, platformname=NEWPLATFORM_B) +print("getTestCaseAssignedTester TC_B TP_A Platform B", response) + +# get bugs for test case TC_AA in test plan A - state TC not executed +response = myTestLink.getTestCaseBugs(newTestPlanID_A, + testcaseexternalid=tc_aa_full_ext_id) +print("getTestCaseBugs TC_AA in TP_A (TC is not executed)", response) + +# report Test Case Results for platform 'Big Bird' with step results +# TC_AA failed, build should be guessed, TC identified with external id +newResult = myTestLink.reportTCResult(newTestPlanID_A, 'f', guess=True, + testcaseexternalid=tc_aa_full_ext_id, + platformname=NEWPLATFORM_A, + execduration=2.9, timestamp='2014-09-18 14:33', + steps=[{'step_number' : 3, 'result' : 'p', 'notes' : 'result note for passed step 3'}, + {'step_number' : 4, 'result' : 'f', 'notes' : 'result note for failed step 4'}] ) +print("reportTCResult", newResult) +newResultID_AA = newResult[0]['id'] + +# get bugs for test case TC_AA in test plan A - state TC is executed +response = myTestLink.getTestCaseBugs(newTestPlanID_A, + testcaseexternalid=tc_aa_full_ext_id) +print("getTestCaseBugs TC_AA in TP_A (TC is executed, no bug)", response) + +# report Test Case Results for platform 'Small Bird' +# TC_AA passed, build should be guessed, TC identified with external id +newResult = myTestLink.reportTCResult(newTestPlanID_A, 'p', guess=True, + testcaseexternalid=tc_aa_full_ext_id, + platformid=newPlatFormID_B, + execduration='3.2', timestamp='2014-09-19 14:33:02') +print("reportTCResult", newResult) +newResultID_AA_p = newResult[0]['id'] +# TC_B passed, explicit build and some notes , TC identified with internal id +newResult = myTestLink.reportTCResult(newTestPlanID_A, 'p', + buildid=newBuildID_A, testcaseid=newTestCaseID_B, + platformname=NEWPLATFORM_B, notes="first try") +print("reportTCResult", newResult) +newResultID_B = newResult[0]['id'] + +#FIXME: know 1.9.20_fixed issue +# testlink.testlinkerrors.TLResponseError: 6002: (uploadExecutionAttachment) - Error inserting attachment on DB +# +# E_WARNING base64_decode() expects parameter 1 to be string, array given +# - in /var/www/html/lib/api/xmlrpc/v1/xmlrpc.class.php - Line 5834 +# +# # add this python file as Attachemnt to last execution of TC_B with +# # different filename 'MyPyExampleApiGeneric.py' +# a_file=open(NEWATTACHMENT_PY) +# newAttachment = myTestLink.uploadExecutionAttachment(a_file, newResultID_B, +# title='Textfile Example', description='Text Attachment Example for a TestCase Execution', +# filename='MyPyExampleApiGeneric.py') +# print("uploadExecutionAttachment", newAttachment) +# # add png file as Attachemnt to last execution of TC_AA +# # !Attention - on WINDOWS use binary mode for none text file +# # see http://docs.python.org/2/tutorial/inputoutput.html#reading-and-writing-files +# a_file=open(NEWATTACHMENT_PNG, mode='rb') +# newAttachment = myTestLink.uploadExecutionAttachment(a_file, newResultID_AA, +# title='PNG Example', description='PNG Attachment Example for a TestCase Execution') +# print("uploadExecutionAttachment", newAttachment) + +# -- Create Build for TestPlan B (uses no platforms) +newBuild = myTestLink.createBuild(newTestPlanID_B, NEWBUILD_B, + buildnotes='Build for TestPlan without platforms', + releasedate='2016-11-30') +print("createBuild", newBuild) +newBuildID_B = newBuild[0]['id'] +print("New Build '%s' - id: %s" % (NEWBUILD_B, newBuildID_B)) + +# assign user to test case execution tasks - test plans without platforms +response = myTestLink.assignTestCaseExecutionTask( myTestUserName, + newTestPlanID_B, tc_b_full_ext_id, buildname=NEWBUILD_B) +print("assignTestCaseExecutionTask", response) + +# get test case assigned tester +response = myTestLink.getTestCaseAssignedTester( + newTestPlanID_B, tc_b_full_ext_id, buildname=NEWBUILD_B) +print("getTestCaseAssignedTester TC_B TP_B no Platform", response) + +# try to remove not assigned tester +response = myTestLink.unassignTestCaseExecutionTask( + newTestPlanID_B, tc_b_full_ext_id, buildname=NEWBUILD_B, + user=myTestUserName2) +print("unassignTestCaseExecutionTask not assigned user", response) +response = myTestLink.getTestCaseAssignedTester( + newTestPlanID_B, tc_b_full_ext_id, buildname=NEWBUILD_B) +print("getTestCaseAssignedTester TC_B TP_B no Platform", response) + +# try to remove all assigned tester +response = myTestLink.unassignTestCaseExecutionTask( + newTestPlanID_B, tc_b_full_ext_id, buildid=newBuildID_B, + action='unassignAll') +print("unassignTestCaseExecutionTask unassignAll", response) +response = myTestLink.getTestCaseAssignedTester( + newTestPlanID_B, tc_b_full_ext_id, buildname=NEWBUILD_B) +print("getTestCaseAssignedTester TC_B TP_B no Platform", response) + +# reassign user to test case execution tasks - test plans without platforms +response = myTestLink.assignTestCaseExecutionTask( myTestUserName, + newTestPlanID_B, tc_b_full_ext_id, buildid=newBuildID_B) +print("assignTestCaseExecutionTask", response) +response = myTestLink.getTestCaseAssignedTester( + newTestPlanID_B, tc_b_full_ext_id, buildname=NEWBUILD_B) +print("getTestCaseAssignedTester TC_B TP_B no Platform", response) + +# TC_B in test plan b (without platform) +# first try failed (with bug), second blocked - all by user myTestUserName +newResult = myTestLink.reportTCResult(newTestPlanID_B, 'f', + buildid=newBuildID_B, testcaseid=newTestCaseID_B, bugid='007', + notes="no birds are singing", user=myTestUserName) +print("reportTCResult", newResult) +newResultID_B_f = newResult[0]['id'] +newResult = myTestLink.reportTCResult(newTestPlanID_B, 'b', + buildid=newBuildID_B, testcaseid=newTestCaseID_B, bugid='008', + notes="hungry birds blocks the execution", user=myTestUserName) +print("reportTCResult", newResult) +newResultID_B_b = newResult[0]['id'] +# get bugs for test case TC_B in test plan B - state TC is executed with bug +response = myTestLink.getTestCaseBugs(newTestPlanID_B, + testcaseid=newTestCaseID_B) +print("getTestCaseBugs TC_B in TP_B (TC is executed with 2 bugs)", response) + + +# now we make a mistake and commit the same result a second time +# and try to delete this mistake +newResult = myTestLink.reportTCResult(newTestPlanID_B, 'b', + buildid=newBuildID_B, testcaseid=newTestCaseID_B, + notes="mistake, commit same result a second time") +print("reportTCResult", newResult) +newResultID_B_b2 = int(newResult[0]['id']) +try: + # if TL configuration allows deletion of executions, no error will occur + response = myTestLink.deleteExecution(newResultID_B_b2) + print("deleteExecution", response) +except TLResponseError as tl_err: + if tl_err.code == 232: + # TL configuration does not allow deletion of executions + pass + else: + # sh..: another problem occurs + raise + +# now we try to change the execution types of the test cases +# - AA from manual -> auto and B from auto -> manual +newResult = myTestLink.setTestCaseExecutionType(tc_aa_full_ext_id, tc_version, + newProjectID, AUTOMATED) +print("setTestCaseExecutionType", response) +newResult = myTestLink.setTestCaseExecutionType(tc_b_full_ext_id, tc_version, + newProjectID, MANUAL) +print("setTestCaseExecutionType", response) + +# create TestPlan C with Platform, Build , TestCase, assigned TestCase +# and delete it +newTestPlan = myTestLink.createTestPlan(NEWTESTPLAN_C, NEWPROJECT, + notes='TestPlan for delete test.', + active=1, public=1) +print("createTestPlan for DeleteTest", newTestPlan) +newTestPlanID_C = newTestPlan[0]['id'] +print("Test Plan '%s' - id: %s" % (NEWTESTPLAN_C,newTestPlanID_C)) +newBuild = myTestLink.createBuild(newTestPlanID_C, NEWBUILD_C, + buildnotes='Build for TestPlan delete test') +print("createBuild for DeleteTest", newBuild) +newBuildID_C = newBuild[0]['id'] +print("Build '%s' - id: %s" % (NEWBUILD_C, newBuildID_C)) +response = myTestLink.addPlatformToTestPlan(newTestPlanID_C, NEWPLATFORM_C) +print("addPlatformToTestPlan", response) +response = myTestLink.addTestCaseToTestPlan(newProjectID, newTestPlanID_C, + tc_aa_full_ext_id, tc_version, platformid=newPlatFormID_C) +print("addTestCaseToTestPlan", response) +response = myTestLink.assignTestCaseExecutionTask( myTestUserName, + newTestPlanID_C, tc_aa_full_ext_id, buildid=newBuildID_C, + platformid=newPlatFormID_C) +print("assignTestCaseExecutionTask", response) +newResult = myTestLink.reportTCResult(newTestPlanID_C, 'p', + buildid=newBuildID_C, testcaseid=newTestCaseID_AA, + platformname=NEWPLATFORM_C, notes="TP delete test") +print("reportTCResult", newResult) +newResultID_B = newResult[0]['id'] + +#FIXME: know 1.9.20_fixed issue +# testlink.testlinkerrors.TLResponseError: 6002: (uploadExecutionAttachment) - Error inserting attachment on DB +# +# E_WARNING base64_decode() expects parameter 1 to be string, array given +# - in /var/www/html/lib/api/xmlrpc/v1/xmlrpc.class.php - Line 5834 +# +# newAttachment = myTestLink.uploadExecutionAttachment(NEWATTACHMENT_PY, newResultID_B, +# title='Textfile Example', filename='MyPyTPDeleteTest.py', +# description='Attachment Example for a TC Execution and TP delete test') +# print("uploadExecutionAttachment", newAttachment) +response = myTestLink.getTotalsForTestPlan(newTestPlanID_C) +print("getTotalsForTestPlan before delete", response) +response = myTestLink.deleteTestPlan(newTestPlanID_C) +print("deleteTestPlan", response) +try: + response = myTestLink.getTotalsForTestPlan(newTestPlanID_C) + print("getTotalsForTestPlan after delete", response) +except TLResponseError as tl_err: + print(tl_err.message) + +# -- Create Build D and copy Testers from Build A +newBuild = myTestLink.createBuild(newTestPlanID_A, NEWBUILD_D, + buildnotes='Build with copied testers from Build ' + NEWBUILD_A, + active=1, open=1, copytestersfrombuild=newBuildID_A) +print("createBuild", newBuild) +newBuildID_D = newBuild[0]['id'] +print("New Build '%s' - id: %s" % (NEWBUILD_D, newBuildID_D)) + +# close build A - buildid must be converted to an integer +response = myTestLink.closeBuild( int(newBuildID_A) ) +print("closeBuild", response) + +# get information - TestProject +response = myTestLink.getTestProjectByName(NEWPROJECT) +print("getTestProjectByName", response) +response = myTestLink.getProjectTestPlans(newProjectID) +print("getProjectTestPlans", response) +response = myTestLink.getFirstLevelTestSuitesForTestProject(newProjectID) +print("getFirstLevelTestSuitesForTestProject", response) +response = myTestLink.getProjectPlatforms(newProjectID) +print("getProjectPlatforms", response) +response = myTestLink.getProjectKeywords(newProjectID) +print("getProjectKeywords", response) + +# get information - TestPlan +response = myTestLink.getTestPlanByName(NEWPROJECT, NEWTESTPLAN_A) +print("getTestPlanByName", response) +response = myTestLink.getTotalsForTestPlan(newTestPlanID_A) +print("getTotalsForTestPlan", response) +response = myTestLink.getBuildsForTestPlan(newTestPlanID_A) +print("getBuildsForTestPlan", response) +response = myTestLink.getLatestBuildForTestPlan(newTestPlanID_A) +print("getLatestBuildForTestPlan", response) +response = myTestLink.getTestPlanPlatforms(newTestPlanID_A) +print("getTestPlanPlatforms", response) +response = myTestLink.getTestSuitesForTestPlan(newTestPlanID_A) +print("getTestSuitesForTestPlan", response) +# get failed Testcases +response = myTestLink.getTestCasesForTestPlan(newTestPlanID_A, executestatus='f') +print("getTestCasesForTestPlan A failed ", response) +# get Testcases for Plattform SmallBird +response = myTestLink.getTestCasesForTestPlan(newTestPlanID_A, platformid=newPlatFormID_B) +print("getTestCasesForTestPlan A SmallBirds", response) + +# get information - TestSuite +response = myTestLink.getTestSuiteByID(newTestSuiteID_B) +print("getTestSuiteByID", response) +response = myTestLink.getTestSuitesForTestSuite(newTestSuiteID_A) +print("getTestSuitesForTestSuite", response) +response = myTestLink.getTestCasesForTestSuite(newTestSuiteID_A, + deep=True, details='full') +print("getTestCasesForTestSuite", response) +response = myTestLink.getTestCasesForTestSuite(newTestSuiteID_B, + deep=False, details='only_id') +print("getTestCasesForTestSuite", response) + +# Update test suite B details - Using Project ID +updatedTestSuite = myTestLink.updateTestSuite(newTestSuiteID_B, + testprojectid=newProjectID, + details="updated Details of the Test Suite B") +print("updateTestSuite", updatedTestSuite) + +# Update test suite A name and order details - Using Project Name +# with TL 1.9.15 this step fails - solution see TL Mantis Ticket 7696 +# +changedNEWTESTSUITE_A = NEWTESTSUITE_A + ' - Changed' +updatedTestSuite = myTestLink.updateTestSuite(newTestSuiteID_A, prefix=NEWPREFIX, + testsuitename = changedNEWTESTSUITE_A, order=1) +print("updateTestSuite", updatedTestSuite) + +# get all test suites, using the same name - test Suite B +response = myTestLink.getTestSuite(NEWTESTSUITE_B, NEWPREFIX) +print("getTestSuite", response) + +# get informationen - TestCase_B +response = myTestLink.getTestCaseIDByName(NEWTESTCASE_B, testprojectname=NEWPROJECT) +print("getTestCaseIDByName", response) +# get informationen - TestCase_AA via Pathname +tcpathname = '::'.join([NEWPROJECT, changedNEWTESTSUITE_A, NEWTESTSUITE_AA, NEWTESTCASE_AA]) +response = myTestLink.getTestCaseIDByName('unknown', testcasepathname=tcpathname) +print("getTestCaseIDByName", response) +# get execution result +response = myTestLink.getLastExecutionResult(newTestPlanID_A, + testcaseexternalid=tc_aa_full_ext_id) +print("getLastExecutionResult", response) +response = myTestLink.getExecutionSet(newTestPlanID_A, + testcaseexternalid=tc_aa_full_ext_id) +print("getExecutionSet", response) +response = myTestLink.getLastExecutionResult(newTestPlanID_A, + testcaseid=newTestCaseID_B) +print("getLastExecutionResult", response) +response = myTestLink.getExecutionSet(newTestPlanID_A, + testcaseid=newTestCaseID_B) +print("getExecutionSet", response) + +response = myTestLink.getLastExecutionResult(newTestPlanID_A, + testcaseid=newTestCaseID_AA, platformid=newPlatFormID_A) +print("getLastExecutionResult", response) +response = myTestLink.getExecutionSet(newTestPlanID_A, + testcaseid=newTestCaseID_AA, platformid=newPlatFormID_A) +print("getExecutionSet", response) + +response = myTestLink.getAllExecutionsResults(newTestPlanID_A, + testcaseid=newTestCaseID_AA, options={'getBugs' : 1}) +print("getAllExecutionsResults", response) +response = myTestLink.getAllExecutionsResults(newTestPlanID_B, + testcaseid=newTestCaseID_B, options={'getBugs' : 1}) +print("getAllExecutionsResults", response) + +response = myTestLink.getExecCountersByBuild(newTestPlanID_A) +print("getExecCountersByBuild", response) +response = myTestLink.getExecCountersByBuild(newTestPlanID_B) +print("getExecCountersByBuild", response) +response = myTestLink.getTestCaseKeywords(testcaseexternalid=tc_b_full_ext_id) +print("getTestCaseKeywords noKeyWords", response) + +# get information - general +response = myTestLink.getFullPath(int(newTestSuiteID_AA)) +print("getFullPath", response) +response = myTestLink.getFullPath([int(newTestCaseID_AA), int(newTestCaseID_B)]) +print("getFullPath", response) + +# attachments +# add png file as Attachment to test project +a_file=open(NEWATTACHMENT_PNG, mode='rb') +newAttachment = myTestLink.uploadTestProjectAttachment(a_file, newProjectID, + title='PNG Example', description='PNG Attachment Example for a TestProject') +print("uploadTestProjectAttachment", newAttachment) +# add png file as Attachnent to test suite A - uploadXyzAttachmemt also file path +newAttachment = myTestLink.uploadTestSuiteAttachment(NEWATTACHMENT_PNG, newTestSuiteID_A, + title='PNG Example', description='PNG Attachment Example for a TestSuite') +print("uploadTestSuiteAttachment", newAttachment) +# get Attachment of test suite A +response = myTestLink.getTestSuiteAttachments(newTestSuiteID_A) +print("getTestSuiteAttachments", response) + +# FIXME: know 1.9.19 issue +# E_WARNING base64_decode() expects parameter 1 to be string, array given +# - in /var/www/html/lib/api/xmlrpc/v1/xmlrpc.class.php - Line 5103 +# +# # add png file as Attachment to test case B +# a_file=open(NEWATTACHMENT_PNG, mode='rb') +# newAttachment = myTestLink.uploadTestCaseAttachment(a_file, newTestCaseID_B, 1, +# title='PNG Example', description='PNG Attachment Example for a TestCase') +# print("uploadTestCaseAttachment", newAttachment) +# # get Attachment of test case B +# # response = myTestLink.getTestCaseAttachments(testcaseexternalid=tc_aa_full_ext_id) +# # print "getTestCaseAttachments", response +# response = myTestLink.getTestCaseAttachments(testcaseid=newTestCaseID_B, version=1) +# print("getTestCaseAttachments v1", response) +# response = myTestLink.getTestCaseAttachments(testcaseid=newTestCaseID_B) +# print("getTestCaseAttachments vNone", response) + +# get requirements for the test project - empty result +response = myTestLink.getRequirements(newProjectID) +print("getRequirements test project", "Sorry currently no requirments", response) + +# get requirements for the test plan - empty result +response = myTestLink.getRequirements(newProjectID, + testplanid = newTestPlanID_A, + platformid = newPlatFormID_B) +print("getRequirements test plan", "Sorry currently no requirments", response) + + +print("") +print("Number of Projects in TestLink: %i " % len(myTestLink.getProjects())) +print("") +for project in myTestLink.getProjects(): + print("Name: %(name)s ID: %(id)s " % project) +print("") + + +# +# +# diff --git a/example/TestLinkExampleGenericApi_Req.py b/example/TestLinkExampleGenericApi_Req.py new file mode 100644 index 0000000..10991b7 --- /dev/null +++ b/example/TestLinkExampleGenericApi_Req.py @@ -0,0 +1,348 @@ +#! /usr/bin/python +# -*- coding: UTF-8 -*- + +# Copyright 2017-2019 Luiko Czub, TestLink-API-Python-client developers +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------ + + +""" + +Shows how to use the TestLinkAPIGeneric for requirements +This example requires a special existing project with special custom fields +assigned and a set of requirements + +a) run example TestLinkExampleGenericApi.py + - this creates a project like PROJECT_API_GENERIC-36 +b) load custom field definitions customFields_ExampleDefs.xml + TL - Desktop - System - Define Custom Fields - Import +c) assign custom fields to project PROJECT_API_GENERIC-36 + TL - Desktop - Test Project - Assign Custom Fields +d) load requirement definitions all-req.xml to project PROJECT_API_GENERIC-36 + TL - Desktop - Requirements - Requirement Specification + +Script works with: + +TestProject PROJECT_API_GENERIC-36 +- TestSuite B - First Level + - TestCase TESTCASE_B +- TestPlan TestPlan_API_GENERIC A (Platform Small Bird) + - Build TestlinkAPIGeneric v0.x.y + +Script creates custom values for TestCase TESTCASE_B +- scope test specification and test execution + +Script returns custom field values from TestPlan and TestSuite, if the user has +added manually some values. + +Cause of missing knowledge, how ids of kind +- requirement and requirement specifications +- testplan - testcase link +could be requested via api, these example does not work currently. + +Script adds keywords KeyWord01 KeyWord02 KeyWord03 to test case TESTCASE_B, +removes keyword KeyWord02 again. + +Script adds keywords KeyWord01 KeyWord02 to test case TESTCASE_AA, +removes keyword KeyWord01 again. + +""" +from testlink import TestlinkAPIGeneric, TestLinkHelper +from testlink.testlinkerrors import TLResponseError +import sys, os.path +from platform import python_version + +# precondition a) +# SERVER_URL and KEY are defined in environment +# TESTLINK_API_PYTHON_SERVER_URL=http://YOURSERVER/testlink/lib/api/xmlrpc.php +# TESTLINK_API_PYTHON_DEVKEY=7ec252ab966ce88fd92c25d08635672b +# +# alternative precondition b) +# SERVEUR_URL and KEY are defined as command line arguments +# python TestLinkExample.py --server_url http://YOURSERVER/testlink/lib/api/xmlrpc.php +# --devKey 7ec252ab966ce88fd92c25d08635672b +# +# ATTENTION: With TestLink 1.9.7, cause of the new REST API, the SERVER_URL +# has changed from +# (old) http://YOURSERVER/testlink/lib/api/xmlrpc.php +# to +# (new) http://YOURSERVER/testlink/lib/api/xmlrpc/v1/xmlrpc.php +tl_helper = TestLinkHelper() +tl_helper.setParamsFromArgs('''Shows how to use the TestLinkAPI for CustomFields. +=> requires an existing project PROJECT_API_GENERIC-*''') +myTestLink = tl_helper.connect(TestlinkAPIGeneric) + +myPyVersion = python_version() +myPyVersionShort = myPyVersion.replace('.', '')[:2] + +NEWTESTPLAN_A="TestPlan_API_GENERIC A" +# NEWTESTPLAN_B="TestPlan_API_GENERIC B" +# NEWTESTPLAN_C="TestPlan_API_GENERIC C - DeleteTest" +# NEWPLATFORM_A='Big Bird %s' % myPyVersionShort +NEWPLATFORM_B='Small Bird' +# NEWPLATFORM_C='Ugly Bird' +NEWTESTSUITE_A="A - First Level" +NEWTESTSUITE_B="B - First Level" +NEWTESTSUITE_AA="AA - Second Level" +NEWTESTCASE_AA="TESTCASE_AA" +NEWTESTCASE_B="TESTCASE_B" +# myApiVersion='%s v%s' % (myTestLink.__class__.__name__ , myTestLink.__version__) +# NEWBUILD_A='%s' % myApiVersion +# NEWBUILD_B='%s' % myApiVersion +# NEWBUILD_C='%s - DeleteTest' % myApiVersion +# NEWBUILD_D='%s - copyTestersTest' % myApiVersion + +this_file_dirname=os.path.dirname(__file__) +NEWATTACHMENT_PY= os.path.join(this_file_dirname, 'TestLinkExampleGenericApi.py') +NEWATTACHMENT_PNG=os.path.join(this_file_dirname, 'PyGreat.png') + +# Servers TestLink Version +myTLVersion = myTestLink.testLinkVersion() +myTLVersionShort = myTLVersion.replace('.', '') + +NEWPROJECT="PROJECT_API_GENERIC-%s" % myPyVersionShort +NEWPREFIX="GPROAPI%s" % myPyVersionShort +ITSNAME="myITS" + +# used connection settings +print( myTestLink.connectionInfo() ) +print( "" ) + +# get information - TestProject +newProject = myTestLink.getTestProjectByName(NEWPROJECT) +print( "getTestProjectByName", newProject ) +newProjectID = newProject['id'] +print( "Project '%s' - id: %s" % (NEWPROJECT,newProjectID) ) +response = myTestLink.getProjectKeywords(newProjectID) +print("getProjectKeywords", response) + +# get information - TestPlan +newTestPlan = myTestLink.getTestPlanByName(NEWPROJECT, NEWTESTPLAN_A) +print( "getTestPlanByName", newTestPlan ) +newTestPlanID_A = newTestPlan[0]['id'] +print( "Test Plan '%s' - id: %s" % (NEWTESTPLAN_A,newTestPlanID_A) ) +response = myTestLink.getTotalsForTestPlan(newTestPlanID_A) +print( "getTotalsForTestPlan", response ) +response = myTestLink.getBuildsForTestPlan(newTestPlanID_A) +print( "getBuildsForTestPlan", response ) +newBuildID_A = response[0]['id'] +newBuildName_A = response[0]['name'] +# get information - TestSuite +response = myTestLink.getTestSuitesForTestPlan(newTestPlanID_A) +print( "getTestSuitesForTestPlan", response ) +newTestSuiteID_A=response[0]['id'] +newTestSuiteID_AA=response[1]['id'] +newTestSuiteID_B=response[2]['id'] +newTestSuite = myTestLink.getTestSuiteByID(newTestSuiteID_B) +print( "getTestSuiteByID", newTestSuite ) +# get informationen - TestCase_B +response = myTestLink.getTestCaseIDByName(NEWTESTCASE_B, testprojectname=NEWPROJECT) +print( "getTestCaseIDByName", response ) +newTestCaseID_B = response[0]['id'] +tc_b_full_ext_id = myTestLink.getTestCase(testcaseid=newTestCaseID_B)[0]['full_tc_external_id'] +print( "Test Case '%s' - id: %s - ext-id %s" % (NEWTESTCASE_B, newTestCaseID_B, tc_b_full_ext_id) ) +# get informationen - TestCase_AA +response = myTestLink.getTestCaseIDByName(NEWTESTCASE_AA, testprojectname=NEWPROJECT) +print( "getTestCaseIDByName", response ) +newTestCaseID_AA = response[0]['id'] +tc_aa_full_ext_id = myTestLink.getTestCase(testcaseid=newTestCaseID_AA)[0]['full_tc_external_id'] +print( "Test Case '%s' - id: %s - ext-id %s" % (NEWTESTCASE_AA, newTestCaseID_AA, tc_aa_full_ext_id) ) + + +# add keywords to TestCase B and TestCase AA +response = myTestLink.addTestCaseKeywords( + {tc_b_full_ext_id : ['KeyWord01', 'KeyWord03', 'KeyWord02'], + tc_aa_full_ext_id : ['KeyWord01', 'KeyWord02', 'KeyWord03']}) +print( "addTestCaseKeywords", response ) +# remove keywords from TestCase B and TestCase AA +response = myTestLink.removeTestCaseKeywords( + {tc_b_full_ext_id : ['KeyWord02'], + tc_aa_full_ext_id : ['KeyWord01', 'KeyWord03']}) +print( "removeTestCaseKeywords", response ) + + +# list test cases with assigned keywords B +response = myTestLink.getTestCasesForTestSuite(newTestSuiteID_B, deep=True, + details='full', getkeywords=True) +print( "getTestCasesForTestSuite B (deep=True)", response ) +response = myTestLink.getTestCasesForTestSuite(newTestSuiteID_B, deep=False, + details='full', getkeywords=True) +print( "getTestCasesForTestSuite B (deep=False)", response ) + +# get informationen - TestCase_B again +newTestCase_B = myTestLink.getTestCase(testcaseid=newTestCaseID_B)[0] +print( "getTestCase B", newTestCase_B ) + +# # return keyword list for TestCase_B - service method TestLinkAPIClient +# response = myTestLink.listKeywordsForTC(newTestCaseID_B) +# print( "listKeywordsForTC B", response ) +# # return keyword lists for all test cases of test newTestSuite_B - service method TestLinkAPIClient +# response = myTestLink.listKeywordsForTS(newTestSuiteID_B) +# print( "listKeywordsForTS B", response ) + +# list test cases with assigned keywords AA +response = myTestLink.getTestCasesForTestSuite(newTestSuiteID_A, deep=True, + details='full', getkeywords=True) +print( "getTestCasesForTestSuite A (deep=True)", response ) +response = myTestLink.getTestCasesForTestSuite(newTestSuiteID_A, deep=False, + details='full', getkeywords=True) +print( "getTestCasesForTestSuite A (deep=False)", response ) + +# get informationen - TestCase_AA again +newTestCase_AA = myTestLink.getTestCase(testcaseid=newTestCaseID_AA)[0] +print( "getTestCase AA", newTestCase_AA ) + +# # return keyword list for TestCase_AA - service method TestLinkAPIClient +# response = myTestLink.listKeywordsForTC(newTestCaseID_AA) +# print( "listKeywordsForTC AA", response ) - service method TestLinkAPIClient +# # return keyword lists for all test cases of test newTestSuite_A +# response = myTestLink.listKeywordsForTS(newTestSuiteID_AA) +# print( "listKeywordsForTS AA", response ) + + +response = myTestLink.getTestCaseKeywords(testcaseid=newTestCaseID_B) +print("getTestCaseKeywords B", response) +response = myTestLink.getTestCaseKeywords(testcaseid=newTestCaseID_AA) +print("getTestCaseKeywords AA", response) + +# new execution result with custom field data +# TC_B passed, explicit build and some notes , TC identified with internal id +newResult = myTestLink.reportTCResult(newTestPlanID_A, 'p', + buildname=newBuildName_A, testcaseid=newTestCaseID_B, + platformname=NEWPLATFORM_B, notes="bugid 4711 is assigned", + bugid='4711', + customfields={'cf_tc_ex_string' : 'a custom exec value set via api', + 'cf_tc_sd_listen' : 'ernie'}) +print( "reportTCResult", newResult ) + +# get execution results +lastResult = myTestLink.getLastExecutionResult(newTestPlanID_A, + testcaseid=newTestCaseID_B, + options={'getBugs' : True})[0] +print( "getLastExecutionResult", lastResult ) + +# get all requirement for the testprojekt +req_list = myTestLink.getRequirements(newProjectID) +print ( "getRequirements all", req_list ) +reqA = req_list[0] +reqB = req_list[1] + +# add requirement reqA to testcase B +# response = myTestLink.assignRequirements(newTestCase_B['full_tc_external_id'], newProjectID, +# [{'req_spec' : reqA['srs_id'], 'requirements' : [ reqA['id'] ]} ] +# ) +print("assignRequirements reqA to TC-B", + "sorry not possible - required srs_id -rec_spec(id) not available") + +# get coverage for requirements reqA +response = myTestLink.getReqCoverage(newProjectID, reqA['req_doc_id']) +print("getReqCoverage reqA", response) + +# add png file as Attachemnt to a requirement specification. +print("uploadRequirementSpecificationAttachment", + "sorry not possible - required srs_id -rec_spec(id) not available") +# add png file as Attachemnt to a requirement. +#a_file=open(NEWATTACHMENT_PNG, mode='rb') +newAttachment = myTestLink.uploadRequirementAttachment(NEWATTACHMENT_PNG, reqA['id'], + title='PNG Example', description='PNG Attachment Example for a requirement') +print("uploadRequirementAttachment", newAttachment) + + +# map of used ids +args = {'devKey' : myTestLink.devKey, + 'testprojectid' : newProjectID, + 'testcaseexternalid' : newTestCase_B['full_tc_external_id'], + 'version' : int(newTestCase_B['version']), + 'tcversion_number' : lastResult['tcversion_number'], + 'executionid' : lastResult['id'], + 'linkid' : 779, + 'testsuiteid': newTestSuiteID_B, + 'testplanid': lastResult['testplan_id'], + 'reqspecid': 7789, +# 'reqspecid': reqA['srs_id'], + 'requirementid': reqA['id'], + 'buildid':newBuildID_A} + +# get CustomField Value - TestCase Execution +response = myTestLink.getTestCaseCustomFieldExecutionValue( + 'cf_tc_ex_string', args['testprojectid'], args['tcversion_number'], + args['executionid'] , args['testplanid'] ) +print( "getTestCaseCustomFieldExecutionValue", response ) + +# update CustomField Value - TestCase SpecDesign +response = myTestLink.updateTestCaseCustomFieldDesignValue( + args['testcaseexternalid'], args['version'], + args['testprojectid'], + {'cf_tc_sd_string' : 'A custom SpecDesign value set via api', + 'cf_tc_sd_list' : 'bibo'}) +print( "updateTestCaseCustomFieldDesignValue", response ) + +# get CustomField Value - TestCase SpecDesign +#response = myTestLink._callServer('getTestCaseCustomFieldDesignValue', args) +response = myTestLink.getTestCaseCustomFieldDesignValue( + args['testcaseexternalid'], args['version'], + args['testprojectid'], 'cf_tc_sd_string', details='full') +print( "getTestCaseCustomFieldDesignValue full", response ) + +response = myTestLink.getTestCaseCustomFieldDesignValue( + args['testcaseexternalid'], args['version'], + args['testprojectid'], 'cf_tc_sd_string', details='value') +print( "getTestCaseCustomFieldDesignValue value", response ) + +response = myTestLink.getTestCaseCustomFieldDesignValue( + args['testcaseexternalid'], args['version'], + args['testprojectid'], 'cf_tc_sd_list', details='simple') +print( "getTestCaseCustomFieldDesignValue simple", response ) + +# get CustomField Value - TestCase Testplan Design +response = myTestLink.getTestCaseCustomFieldTestPlanDesignValue( + 'cf_tc_pd_string', args['testprojectid'], args['tcversion_number'], + args['testplanid'], args['linkid']) +print( "getTestCaseCustomFieldTestPlanDesignValue", response ) + +# update CustomField Value - TestSuite SpecDesign +response = myTestLink.updateTestSuiteCustomFieldDesignValue( + args['testprojectid'], args['testsuiteid'], + {'cf_ts_string' : 'A custom TestSuite value set via api'}) +print( "updateTestSuiteCustomFieldDesignValue", response ) + +# get CustomField Value - TestSuite +response = myTestLink.getTestSuiteCustomFieldDesignValue( + 'cf_ts_string', args['testprojectid'], args['testsuiteid']) +print( "getTestSuiteCustomFieldDesignValue", response ) + +# get CustomField Value - TestPlan +response = myTestLink.getTestPlanCustomFieldDesignValue( + 'cf_tp_string', args['testprojectid'], args['testplanid']) +print( "getTestPlanCustomFieldDesignValue", response ) + +# get CustomField Value - Requirement Specification +response = myTestLink.getReqSpecCustomFieldDesignValue( + 'cf_req_sd_string', args['testprojectid'], args['reqspecid']) +print( "getReqSpecCustomFieldDesignValue", response ) + + +# get CustomField Value - Requirement Specification +response = myTestLink.getRequirementCustomFieldDesignValue( + 'cf_req_string',args['testprojectid'], args['requirementid']) +print( "getRequirementCustomFieldDesignValue", response ) + +# update CustomField Value - Build +response = myTestLink.updateBuildCustomFieldsValues( + args['testprojectid'], args['testplanid'], args['buildid'], + {'cf_b_string' : 'A custom Build value set via api'}) +print( "updateBuildCustomFieldsValues", response ) + + diff --git a/example/TestLinkExample_CF_KW.py b/example/TestLinkExample_CF_KW.py index e276942..b7e19aa 100644 --- a/example/TestLinkExample_CF_KW.py +++ b/example/TestLinkExample_CF_KW.py @@ -1,305 +1,326 @@ -#! /usr/bin/python -# -*- coding: UTF-8 -*- - -# Copyright 2013-2015 Luiko Czub, TestLink-API-Python-client developers -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# ------------------------------------------------------------------------ - - -""" - -Shows how to use the TestLinkAPI for custom fields -This example requires a special existing project with special custom fields -assigned - -a) run example TestLinkExample.py - - this creates a project like NEW_PROJECT_API-34 - if some additional project are created since running that example, adapt - variable projNr in this script your are reading currently -b) load custom field definitions customFields_ExampleDefs.xml - TL - Desktop - System - Define Custom Fields - Import -c) assign custom fields to project NEW_PROJECT_API-34 - TL - Desktop - Test Project - Assign Custom Fields -d) load keyword definitions keywords_ExampleDefs.xml - TL - Desktop - Test Project - Keyword Management - -Script works with: - -TestProject NEW_PROJECT_API-34 -- TestSuite B - First Level - - TestCase TESTCASE_B -- TestPlan TestPlan_API_GENERIC A (Platform Small Bird) - - Build TestlinkAPIGeneric v0.x.y - -Script creates custom values for TestCase TESTCASE_B -- scope test specification and test execution - -Script returns custom field values from TestPlan and TestSuite, if the user has -added manually some values. - -Cause of missing knowledge, how ids of kind -- requirement and requirement specifications -- testplan - testcase link -could be requested via api, these example does not work currently. - -Script adds keywords KeyWord01 KeyWord02 KeyWord03 to test case TESTCASE_B, -removes keyword KeyWord02 again. - -Script adds keywords KeyWord01 KeyWord02 to test case TESTCASE_AA, -removes keyword KeyWord01 again. - -""" -from testlink import TestlinkAPIClient, TestLinkHelper -from testlink.testlinkerrors import TLResponseError -import sys, os.path -from platform import python_version - -# precondition a) -# SERVER_URL and KEY are defined in environment -# TESTLINK_API_PYTHON_SERVER_URL=http://YOURSERVER/testlink/lib/api/xmlrpc.php -# TESTLINK_API_PYTHON_DEVKEY=7ec252ab966ce88fd92c25d08635672b -# -# alternative precondition b) -# SERVEUR_URL and KEY are defined as command line arguments -# python TestLinkExample.py --server_url http://YOURSERVER/testlink/lib/api/xmlrpc.php -# --devKey 7ec252ab966ce88fd92c25d08635672b -# -# ATTENTION: With TestLink 1.9.7, cause of the new REST API, the SERVER_URL -# has changed from -# (old) http://YOURSERVER/testlink/lib/api/xmlrpc.php -# to -# (new) http://YOURSERVER/testlink/lib/api/xmlrpc/v1/xmlrpc.php -tl_helper = TestLinkHelper() -tl_helper.setParamsFromArgs('''Shows how to use the TestLinkAPI for CustomFields. -=> requires an existing project NEW_PROJECT_API-*''') -myTestLink = tl_helper.connect(TestlinkAPIClient) - -myPyVersion = python_version() -myPyVersionShort = myPyVersion.replace('.', '')[:2] - -NEWPROJECT="NEW_PROJECT_API-%s" % myPyVersionShort -NEWPREFIX="NPROAPI%s" % myPyVersionShort -NEWTESTPLAN_A="TestPlan_API A" -# NEWTESTPLAN_B="TestPlan_API B" -# NEWPLATFORM_A='Big Bird %i' % projNr -NEWPLATFORM_B='Small Birds' -# NEWPLATFORM_C='Ugly Bird' -NEWTESTSUITE_A="A - First Level" -NEWTESTSUITE_B="B - First Level" -NEWTESTSUITE_AA="AA - Second Level" -NEWTESTCASE_AA="TESTCASE_AA" -NEWTESTCASE_B="TESTCASE_B" -# myApiVersion='%s v%s' % (myTestLink.__class__.__name__ , myTestLink.__version__) -# NEWBUILD_A='%s' % myApiVersion -# NEWBUILD_B='%s' % myApiVersion - -NEWATTACHMENT_PY= os.path.realpath(__file__) -this_file_dirname=os.path.dirname(NEWATTACHMENT_PY) -NEWATTACHMENT_PNG=os.path.join(this_file_dirname, 'PyGreat.png') - -# Servers TestLink Version -myTLVersion = myTestLink.testLinkVersion() - -# used connection settings -print( myTestLink.connectionInfo() ) -print( "" ) - -# get information - TestProject -newProject = myTestLink.getTestProjectByName(NEWPROJECT) -print( "getTestProjectByName", newProject ) -newProjectID = newProject['id'] -print( "Project '%s' - id: %s" % (NEWPROJECT,newProjectID) ) -response = myTestLink.getProjectKeywords(newProjectID) -print("getProjectKeywords", response) - -# get information - TestPlan -newTestPlan = myTestLink.getTestPlanByName(NEWPROJECT, NEWTESTPLAN_A) -print( "getTestPlanByName", newTestPlan ) -newTestPlanID_A = newTestPlan[0]['id'] -print( "Test Plan '%s' - id: %s" % (NEWTESTPLAN_A,newTestPlanID_A) ) -response = myTestLink.getTotalsForTestPlan(newTestPlanID_A) -print( "getTotalsForTestPlan", response ) -response = myTestLink.getBuildsForTestPlan(newTestPlanID_A) -print( "getBuildsForTestPlan", response ) -newBuildID_A = response[0]['id'] -newBuildName_A = response[0]['name'] -# get information - TestSuite -response = myTestLink.getTestSuitesForTestPlan(newTestPlanID_A) -print( "getTestSuitesForTestPlan", response ) -newTestSuiteID_A=response[0]['id'] -newTestSuiteID_AA=response[1]['id'] -newTestSuiteID_B=response[2]['id'] -newTestSuite = myTestLink.getTestSuiteByID(newTestSuiteID_B) -print( "getTestSuiteByID", newTestSuite ) -# get informationen - TestCase_B -response = myTestLink.getTestCaseIDByName(NEWTESTCASE_B, testprojectname=NEWPROJECT) -print( "getTestCaseIDByName", response ) -newTestCaseID_B = response[0]['id'] -tc_b_full_ext_id = myTestLink.getTestCase(newTestCaseID_B)[0]['full_tc_external_id'] -print( "Test Case '%s' - id: %s - ext-id %s" % (NEWTESTCASE_B, newTestCaseID_B, tc_b_full_ext_id) ) -# get informationen - TestCase_AA -response = myTestLink.getTestCaseIDByName(NEWTESTCASE_AA, testprojectname=NEWPROJECT) -print( "getTestCaseIDByName", response ) -newTestCaseID_AA = response[0]['id'] -tc_aa_full_ext_id = myTestLink.getTestCase(newTestCaseID_AA)[0]['full_tc_external_id'] -print( "Test Case '%s' - id: %s - ext-id %s" % (NEWTESTCASE_AA, newTestCaseID_AA, tc_aa_full_ext_id) ) - - -# add keywords to TestCase B and TestCase AA -response = myTestLink.addTestCaseKeywords( - {tc_b_full_ext_id : ['KeyWord01', 'KeyWord03', 'KeyWord02'], - tc_aa_full_ext_id : ['KeyWord01', 'KeyWord02', 'KeyWord03']}) -print( "addTestCaseKeywords", response ) -# remove keywords from TestCase B and TestCase AA -response = myTestLink.removeTestCaseKeywords( - {tc_b_full_ext_id : ['KeyWord02'], - tc_aa_full_ext_id : ['KeyWord01', 'KeyWord03']}) -print( "removeTestCaseKeywords", response ) - - -# list test cases with assigned keywords B -response = myTestLink.getTestCasesForTestSuite(newTestSuiteID_B, True, - 'full', getkeywords=True) -print( "getTestCasesForTestSuite B (deep=True)", response ) -response = myTestLink.getTestCasesForTestSuite(newTestSuiteID_B, False, - 'full', getkeywords=True) -print( "getTestCasesForTestSuite B (deep=False)", response ) - -# get informationen - TestCase_B again -newTestCase_B = myTestLink.getTestCase(testcaseid=newTestCaseID_B)[0] -print( "getTestCase B", newTestCase_B ) - -# return keyword list for TestCase_B -response = myTestLink.listKeywordsForTC(newTestCaseID_B) -print( "listKeywordsForTC B", response ) -# return keyword lists for all test cases of test newTestSuite_B -response = myTestLink.listKeywordsForTS(newTestSuiteID_B) -print( "listKeywordsForTS B", response ) - -# list test cases with assigned keywords AA -response = myTestLink.getTestCasesForTestSuite(newTestSuiteID_A, True, - 'full', getkeywords=True) -print( "getTestCasesForTestSuite A (deep=True)", response ) -response = myTestLink.getTestCasesForTestSuite(newTestSuiteID_A, False, - 'full', getkeywords=True) -print( "getTestCasesForTestSuite A (deep=False)", response ) - -# get informationen - TestCase_AA again -newTestCase_AA = myTestLink.getTestCase(testcaseid=newTestCaseID_AA)[0] -print( "getTestCase AA", newTestCase_AA ) - -# return keyword list for TestCase_AA -response = myTestLink.listKeywordsForTC(newTestCaseID_AA) -print( "listKeywordsForTC AA", response ) -# return keyword lists for all test cases of test newTestSuite_A -response = myTestLink.listKeywordsForTS(newTestSuiteID_AA) -print( "listKeywordsForTS AA", response ) - - -response = myTestLink.getTestCaseKeywords(testcaseid=newTestCaseID_B) -print("getTestCaseKeywords B", response) -response = myTestLink.getTestCaseKeywords(testcaseid=newTestCaseID_AA) -print("getTestCaseKeywords AA", response) - -# new execution result with custom field data -# TC_B passed, explicit build and some notes , TC identified with internal id -newResult = myTestLink.reportTCResult(newTestCaseID_B, newTestPlanID_A, - newBuildName_A, 'p', "bugid 4711 is assigned", - platformname=NEWPLATFORM_B, bugid='4711', - customfields={'cf_tc_ex_string' : 'a custom exec value set via api', - 'cf_tc_sd_listen' : 'ernie'}) -print( "reportTCResult", newResult ) - -# get execution results -lastResult = myTestLink.getLastExecutionResult(newTestPlanID_A, newTestCaseID_B, - options={'getBugs' : True})[0] -print( "getLastExecutionResult", lastResult ) - -# map of used ids -args = {'devKey' : myTestLink.devKey, - 'testprojectid' : newProjectID, - 'testcaseexternalid' : newTestCase_B['full_tc_external_id'], - 'version' : int(newTestCase_B['version']), - 'tcversion_number' : lastResult['tcversion_number'], - 'executionid' : lastResult['id'], - 'linkid' : 779, - 'testsuiteid': newTestSuiteID_B, - 'testplanid': lastResult['testplan_id'], - 'reqspecid': 7789, - 'requirementid': 7791} - -# get CustomField Value - TestCase Execution -response = myTestLink.getTestCaseCustomFieldExecutionValue( - 'cf_tc_ex_string', args['testprojectid'], args['tcversion_number'], - args['executionid'] , args['testplanid'] ) -print( "getTestCaseCustomFieldExecutionValue", response ) - -# update CustomField Value - TestCase SpecDesign -response = myTestLink.updateTestCaseCustomFieldDesignValue( - args['testcaseexternalid'], args['version'], - args['testprojectid'], - {'cf_tc_sd_string' : 'A custom SpecDesign value set via api', - 'cf_tc_sd_list' : 'bibo'}) -print( "updateTestCaseCustomFieldDesignValue", response ) - -# get CustomField Value - TestCase SpecDesign -#response = myTestLink._callServer('getTestCaseCustomFieldDesignValue', args) -response = myTestLink.getTestCaseCustomFieldDesignValue( - args['testcaseexternalid'], args['version'], - args['testprojectid'], 'cf_tc_sd_string', 'full') -print( "getTestCaseCustomFieldDesignValue full", response ) - -response = myTestLink.getTestCaseCustomFieldDesignValue( - args['testcaseexternalid'], args['version'], - args['testprojectid'], 'cf_tc_sd_string', 'value') -print( "getTestCaseCustomFieldDesignValue value", response ) - -response = myTestLink.getTestCaseCustomFieldDesignValue( - args['testcaseexternalid'], args['version'], - args['testprojectid'], 'cf_tc_sd_list', 'simple') -print( "getTestCaseCustomFieldDesignValue simple", response ) - -# get CustomField Value - TestCase Testplan Design -response = myTestLink.getTestCaseCustomFieldTestPlanDesignValue( - 'cf_tc_pd_string', args['testprojectid'], args['tcversion_number'], - args['testplanid'], args['linkid']) -print( "getTestCaseCustomFieldTestPlanDesignValue", response ) - -# update CustomField Value - TestSuite SpecDesign -response = myTestLink.updateTestSuiteCustomFieldDesignValue( - args['testprojectid'], args['testsuiteid'], - {'cf_ts_string' : 'A custom TestSuite value set via api'}) -print( "updateTestSuiteCustomFieldDesignValue", response ) - -# get CustomField Value - TestSuite -response = myTestLink.getTestSuiteCustomFieldDesignValue( - 'cf_ts_string', args['testprojectid'], args['testsuiteid']) -print( "getTestSuiteCustomFieldDesignValue", response ) - -# get CustomField Value - TestPlan -response = myTestLink.getTestPlanCustomFieldDesignValue( - 'cf_tp_string', args['testprojectid'], args['testplanid']) -print( "getTestPlanCustomFieldDesignValue", response ) - -# get CustomField Value - Requirement Specification -response = myTestLink.getReqSpecCustomFieldDesignValue( - 'cf_req_sd_string', args['testprojectid'], args['reqspecid']) -print( "getReqSpecCustomFieldDesignValue", response ) - - -# get CustomField Value - Requirement Specification -response = myTestLink.getRequirementCustomFieldDesignValue( - 'cf_req_string',args['testprojectid'], args['requirementid']) -print( "getRequirementCustomFieldDesignValue", response ) +#! /usr/bin/python +# -*- coding: UTF-8 -*- + +# Copyright 2013-2019 Luiko Czub, TestLink-API-Python-client developers +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------ + + +""" + +Shows how to use the TestLinkAPI for custom fields +This example requires a special existing project with special custom fields +assigned + +a) run example TestLinkExample.py + - this creates a project like NEW_PROJECT_API-37 +b) load custom field definitions customFields_ExampleDefs.xml + TL - Desktop - System - Define Custom Fields - Import +c) assign custom fields to project NEW_PROJECT_API-37 + TL - Desktop - Test Project - Assign Custom Fields +d) load keyword definitions keywords_ExampleDefs.xml + TL - Desktop - Test Project - Keyword Management + +Script works with: + +TestProject NEW_PROJECT_API-37 +- TestSuite B - First Level + - TestCase TESTCASE_B +- TestPlan TestPlan_API A (Platform Small Bird) + - Build TestlinkAPIClient v0.x.y + +Script creates custom values for TestCase TESTCASE_B +- scope test specification and test execution + +Script returns custom field values from TestPlan and TestSuite, if the user has +added manually some values. + +Cause of missing knowledge, how ids of kind +- requirement and requirement specifications +- testplan - testcase link +could be requested via api, these example does not work currently. + +Script adds keywords KeyWord01 KeyWord02 KeyWord03 to test case TESTCASE_B, +removes keyword KeyWord02 again. + +Script adds keywords KeyWord01 KeyWord02 to test case TESTCASE_AA, +removes keyword KeyWord01 again. + +""" +from testlink import TestlinkAPIClient, TestLinkHelper +from testlink.testlinkerrors import TLResponseError +import sys, os.path +from platform import python_version + +# precondition a) +# SERVER_URL and KEY are defined in environment +# TESTLINK_API_PYTHON_SERVER_URL=http://YOURSERVER/testlink/lib/api/xmlrpc.php +# TESTLINK_API_PYTHON_DEVKEY=7ec252ab966ce88fd92c25d08635672b +# +# alternative precondition b) +# SERVEUR_URL and KEY are defined as command line arguments +# python TestLinkExample.py --server_url http://YOURSERVER/testlink/lib/api/xmlrpc.php +# --devKey 7ec252ab966ce88fd92c25d08635672b +# +# ATTENTION: With TestLink 1.9.7, cause of the new REST API, the SERVER_URL +# has changed from +# (old) http://YOURSERVER/testlink/lib/api/xmlrpc.php +# to +# (new) http://YOURSERVER/testlink/lib/api/xmlrpc/v1/xmlrpc.php +tl_helper = TestLinkHelper() +tl_helper.setParamsFromArgs('''Shows how to use the TestLinkAPI for CustomFields. +=> requires an existing project NEW_PROJECT_API-*''') +myTestLink = tl_helper.connect(TestlinkAPIClient) + +myPyVersion = python_version() +myPyVersionShort = myPyVersion.replace('.', '')[:2] + +NEWTESTPLAN_A="TestPlan_API A" +# NEWTESTPLAN_B="TestPlan_API B" +# NEWTESTPLAN_C="TestPlan_API C - DeleteTest" +# NEWPLATFORM_A='Big Birds %s' % myPyVersionShort +NEWPLATFORM_B='Small Birds' +# NEWPLATFORM_C='Ugly Birds' +NEWTESTSUITE_A="A - First Level" +NEWTESTSUITE_B="B - First Level" +NEWTESTSUITE_AA="AA - Second Level" +NEWTESTCASE_AA="TESTCASE_AA" +NEWTESTCASE_B="TESTCASE_B" +# myApiVersion='%s v%s' % (myTestLink.__class__.__name__ , myTestLink.__version__) +# NEWBUILD_A='%s' % myApiVersion +# NEWBUILD_B='%s' % myApiVersion +# NEWBUILD_C='%s - DeleteTest' % myApiVersion +# NEWBUILD_D='%s - copyTestersTest' % myApiVersion + +this_file_dirname=os.path.dirname(__file__) +NEWATTACHMENT_PY= os.path.join(this_file_dirname, 'TestLinkExample.py') +NEWATTACHMENT_PNG=os.path.join(this_file_dirname, 'PyGreat.png') + +# Servers TestLink Version +myTLVersion = myTestLink.testLinkVersion() +myTLVersionShort = myTLVersion.replace('.', '') + +NEWPROJECT="NEW_PROJECT_API-%s" % myPyVersionShort +NEWPREFIX="NPROAPI%s" % myPyVersionShort +ITSNAME="myITS" + +# used connection settings +print( myTestLink.connectionInfo() ) +print( "" ) + +# get information - TestProject +newProject = myTestLink.getTestProjectByName(NEWPROJECT) +print( "getTestProjectByName", newProject ) +newProjectID = newProject['id'] +print( "Project '%s' - id: %s" % (NEWPROJECT,newProjectID) ) +response = myTestLink.getProjectKeywords(newProjectID) +print("getProjectKeywords", response) + +# get information - TestPlan +newTestPlan = myTestLink.getTestPlanByName(NEWPROJECT, NEWTESTPLAN_A) +print( "getTestPlanByName", newTestPlan ) +newTestPlanID_A = newTestPlan[0]['id'] +print( "Test Plan '%s' - id: %s" % (NEWTESTPLAN_A,newTestPlanID_A) ) +response = myTestLink.getTotalsForTestPlan(newTestPlanID_A) +print( "getTotalsForTestPlan", response ) +response = myTestLink.getBuildsForTestPlan(newTestPlanID_A) +print( "getBuildsForTestPlan", response ) +newBuildID_A = response[0]['id'] +newBuildName_A = response[0]['name'] +# get information - TestSuite +response = myTestLink.getTestSuitesForTestPlan(newTestPlanID_A) +print( "getTestSuitesForTestPlan", response ) +newTestSuiteID_A=response[0]['id'] +newTestSuiteID_AA=response[1]['id'] +newTestSuiteID_B=response[2]['id'] +newTestSuite = myTestLink.getTestSuiteByID(newTestSuiteID_B) +print( "getTestSuiteByID", newTestSuite ) +# get informationen - TestCase_B +response = myTestLink.getTestCaseIDByName(NEWTESTCASE_B, testprojectname=NEWPROJECT) +print( "getTestCaseIDByName", response ) +newTestCaseID_B = response[0]['id'] +tc_b_full_ext_id = myTestLink.getTestCase(newTestCaseID_B)[0]['full_tc_external_id'] +print( "Test Case '%s' - id: %s - ext-id %s" % (NEWTESTCASE_B, newTestCaseID_B, tc_b_full_ext_id) ) +# get informationen - TestCase_AA +response = myTestLink.getTestCaseIDByName(NEWTESTCASE_AA, testprojectname=NEWPROJECT) +print( "getTestCaseIDByName", response ) +newTestCaseID_AA = response[0]['id'] +tc_aa_full_ext_id = myTestLink.getTestCase(newTestCaseID_AA)[0]['full_tc_external_id'] +print( "Test Case '%s' - id: %s - ext-id %s" % (NEWTESTCASE_AA, newTestCaseID_AA, tc_aa_full_ext_id) ) + + +# add keywords to TestCase B and TestCase AA +response = myTestLink.addTestCaseKeywords( + {tc_b_full_ext_id : ['KeyWord01', 'KeyWord03', 'KeyWord02'], + tc_aa_full_ext_id : ['KeyWord01', 'KeyWord02', 'KeyWord03']}) +print( "addTestCaseKeywords", response ) +# remove keywords from TestCase B and TestCase AA +response = myTestLink.removeTestCaseKeywords( + {tc_b_full_ext_id : ['KeyWord02'], + tc_aa_full_ext_id : ['KeyWord01', 'KeyWord03']}) +print( "removeTestCaseKeywords", response ) + + +# list test cases with assigned keywords B +response = myTestLink.getTestCasesForTestSuite(newTestSuiteID_B, True, + 'full', getkeywords=True) +print( "getTestCasesForTestSuite B (deep=True)", response ) +response = myTestLink.getTestCasesForTestSuite(newTestSuiteID_B, False, + 'full', getkeywords=True) +print( "getTestCasesForTestSuite B (deep=False)", response ) + +# get informationen - TestCase_B again +newTestCase_B = myTestLink.getTestCase(testcaseid=newTestCaseID_B)[0] +print( "getTestCase B", newTestCase_B ) + +# return keyword list for TestCase_B +response = myTestLink.listKeywordsForTC(newTestCaseID_B) +print( "listKeywordsForTC B", response ) +# return keyword lists for all test cases of test newTestSuite_B +response = myTestLink.listKeywordsForTS(newTestSuiteID_B) +print( "listKeywordsForTS B", response ) + +# list test cases with assigned keywords AA +response = myTestLink.getTestCasesForTestSuite(newTestSuiteID_A, True, + 'full', getkeywords=True) +print( "getTestCasesForTestSuite A (deep=True)", response ) +response = myTestLink.getTestCasesForTestSuite(newTestSuiteID_A, False, + 'full', getkeywords=True) +print( "getTestCasesForTestSuite A (deep=False)", response ) + +# get informationen - TestCase_AA again +newTestCase_AA = myTestLink.getTestCase(testcaseid=newTestCaseID_AA)[0] +print( "getTestCase AA", newTestCase_AA ) + +# return keyword list for TestCase_AA +response = myTestLink.listKeywordsForTC(newTestCaseID_AA) +print( "listKeywordsForTC AA", response ) +# return keyword lists for all test cases of test newTestSuite_A +response = myTestLink.listKeywordsForTS(newTestSuiteID_AA) +print( "listKeywordsForTS AA", response ) + + +response = myTestLink.getTestCaseKeywords(testcaseid=newTestCaseID_B) +print("getTestCaseKeywords B", response) +response = myTestLink.getTestCaseKeywords(testcaseid=newTestCaseID_AA) +print("getTestCaseKeywords AA", response) + +# new execution result with custom field data +# TC_B passed, explicit build and some notes , TC identified with internal id +newResult = myTestLink.reportTCResult(newTestCaseID_B, newTestPlanID_A, + newBuildName_A, 'p', "bugid 4711 is assigned", + platformname=NEWPLATFORM_B, bugid='4711', + customfields={'cf_tc_ex_string' : 'a custom exec value set via api', + 'cf_tc_sd_listen' : 'ernie'}) +print( "reportTCResult", newResult ) + +# get execution results +lastResult = myTestLink.getLastExecutionResult(newTestPlanID_A, newTestCaseID_B, + options={'getBugs' : True})[0] +print( "getLastExecutionResult", lastResult ) + +# map of used ids +args = {'devKey' : myTestLink.devKey, + 'testprojectid' : newProjectID, + 'testcaseexternalid' : newTestCase_B['full_tc_external_id'], + 'version' : int(newTestCase_B['version']), + 'tcversion_number' : lastResult['tcversion_number'], + 'executionid' : lastResult['id'], + 'linkid' : 779, + 'testsuiteid': newTestSuiteID_B, + 'testplanid': lastResult['testplan_id'], + 'reqspecid': 7789, + 'requirementid': 7791, + 'buildid':newBuildID_A} + +# get CustomField Value - TestCase Execution +response = myTestLink.getTestCaseCustomFieldExecutionValue( + 'cf_tc_ex_string', args['testprojectid'], args['tcversion_number'], + args['executionid'] , args['testplanid'] ) +print( "getTestCaseCustomFieldExecutionValue", response ) + +# update CustomField Value - TestCase SpecDesign +response = myTestLink.updateTestCaseCustomFieldDesignValue( + args['testcaseexternalid'], args['version'], + args['testprojectid'], + {'cf_tc_sd_string' : 'A custom SpecDesign value set via api', + 'cf_tc_sd_list' : 'bibo'}) +print( "updateTestCaseCustomFieldDesignValue", response ) + +# get CustomField Value - TestCase SpecDesign +#response = myTestLink._callServer('getTestCaseCustomFieldDesignValue', args) +response = myTestLink.getTestCaseCustomFieldDesignValue( + args['testcaseexternalid'], args['version'], + args['testprojectid'], 'cf_tc_sd_string', 'full') +print( "getTestCaseCustomFieldDesignValue full", response ) + +response = myTestLink.getTestCaseCustomFieldDesignValue( + args['testcaseexternalid'], args['version'], + args['testprojectid'], 'cf_tc_sd_string', 'value') +print( "getTestCaseCustomFieldDesignValue value", response ) + +response = myTestLink.getTestCaseCustomFieldDesignValue( + args['testcaseexternalid'], args['version'], + args['testprojectid'], 'cf_tc_sd_list', 'simple') +print( "getTestCaseCustomFieldDesignValue simple", response ) + +# get CustomField Value - TestCase Testplan Design +response = myTestLink.getTestCaseCustomFieldTestPlanDesignValue( + 'cf_tc_pd_string', args['testprojectid'], args['tcversion_number'], + args['testplanid'], args['linkid']) +print( "getTestCaseCustomFieldTestPlanDesignValue", response ) + +# get CustomFields Values from all test cases of a TestPlan +response = myTestLink.getTestCasesForTestPlan(args['testplanid'], + customfields=True) +print("getTestCasesForTestPlan with all customfields", response) +response = myTestLink.getTestCasesForTestPlan(args['testplanid'], + customfields=['cf_tc_sd_string']) +print("getTestCasesForTestPlan with specific customfields", response) + +# update CustomField Value - TestSuite SpecDesign +response = myTestLink.updateTestSuiteCustomFieldDesignValue( + args['testprojectid'], args['testsuiteid'], + {'cf_ts_string' : 'A custom TestSuite value set via api'}) +print( "updateTestSuiteCustomFieldDesignValue", response ) + +# get CustomField Value - TestSuite +response = myTestLink.getTestSuiteCustomFieldDesignValue( + 'cf_ts_string', args['testprojectid'], args['testsuiteid']) +print( "getTestSuiteCustomFieldDesignValue", response ) + +# get CustomField Value - TestPlan +response = myTestLink.getTestPlanCustomFieldDesignValue( + 'cf_tp_string', args['testprojectid'], args['testplanid']) +print( "getTestPlanCustomFieldDesignValue", response ) + +# get CustomField Value - Requirement Specification +response = myTestLink.getReqSpecCustomFieldDesignValue( + 'cf_req_sd_string', args['testprojectid'], args['reqspecid']) +print( "getReqSpecCustomFieldDesignValue", response ) + + +# get CustomField Value - Requirement Specification +response = myTestLink.getRequirementCustomFieldDesignValue( + 'cf_req_string',args['testprojectid'], args['requirementid']) +print( "getRequirementCustomFieldDesignValue", response ) + +# update CustomField Value - Build +response = myTestLink.updateBuildCustomFieldsValues( + args['testprojectid'], args['testplanid'], args['buildid'], + {'cf_b_string' : 'A custom Build value set via api'}) +print( "updateBuildCustomFieldsValues", response ) + + diff --git a/example/all-req.xml b/example/all-req.xml new file mode 100644 index 0000000..1a1b4f8 --- /dev/null +++ b/example/all-req.xml @@ -0,0 +1,123 @@ + + + + + + + + + + + +a section

]]> +
+ + + <![CDATA[use case 01]]> + 1 + 1 + 1 + a use case

]]>
+ + + + +
+ + + <![CDATA[none function requirement 01]]> + 1 + 1 + 2 + a none functional requirement

]]>
+ + + + +
+ + none-function-01 + use-case-01 + 2 + +
+ + + + + + + + +a user requirement

]]> +
+ + + <![CDATA[restriction 01]]> + 1 + 1 + 1 + a restriction

]]>
+ + + + +
+ + + <![CDATA[user gui 01]]> + 1 + 1 + 2 + a user gui

]]>
+ + + + +
+ + system-func-01 + user-gui-01 + 2 + +
+ + + + + + + + +a system requirement

]]> +
+ + + <![CDATA[system function 01]]> + 1 + 1 + 2 + a system function

]]>
+ + + + +
+ + + <![CDATA[feature 01]]> + 1 + 1 + 3 + a feature

]]>
+ + + + +
+ + system-func-01 + user-gui-01 + 2 + +
diff --git a/example/customFields_ExampleDefs.xml b/example/customFields_ExampleDefs.xml index 54a5635..02ca574 100644 --- a/example/customFields_ExampleDefs.xml +++ b/example/customFields_ExampleDefs.xml @@ -136,4 +136,21 @@ + + + + + + + + + + + + + + + + + diff --git a/robot/TestlinkAPILibrary.py b/robot/TestlinkAPILibrary.py index 5074dd1..41f0f99 100644 --- a/robot/TestlinkAPILibrary.py +++ b/robot/TestlinkAPILibrary.py @@ -1,7 +1,7 @@ #! /usr/bin/python # -*- coding: UTF-8 -*- -# Copyright 2014 Luiko Czub +# Copyright 2014-2019 Luiko Czub # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/robot/TestlinkSeLibExtension.py b/robot/TestlinkSeLibExtension.py index e0b8cbb..0f291f2 100644 --- a/robot/TestlinkSeLibExtension.py +++ b/robot/TestlinkSeLibExtension.py @@ -1,7 +1,7 @@ #! /usr/bin/python # -*- coding: UTF-8 -*- -# Copyright 2014 Luiko Czub +# Copyright 2014-2019 Luiko Czub # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/robot/tlapi.robot b/robot/tlapi.robot index 94df447..069c8c1 100644 --- a/robot/tlapi.robot +++ b/robot/tlapi.robot @@ -1,4 +1,4 @@ -# Copyright 2014 Luiko Czub +# Copyright 2014-2019 Luiko Czub # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/robot/tlweb.robot b/robot/tlweb.robot index 784fa54..f7d4900 100644 --- a/robot/tlweb.robot +++ b/robot/tlweb.robot @@ -1,4 +1,4 @@ -# Copyright 2014 Luiko Czub +# Copyright 2014-2019 Luiko Czub # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/robot/tlweb_resource.robot b/robot/tlweb_resource.robot index 94bcda4..bdc7a8c 100644 --- a/robot/tlweb_resource.robot +++ b/robot/tlweb_resource.robot @@ -1,4 +1,4 @@ -# Copyright 2014 Luiko Czub +# Copyright 2014-2019 Luiko Czub # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/setup.py b/setup.py index 660d584..86ced60 100644 --- a/setup.py +++ b/setup.py @@ -1,7 +1,7 @@ #! /usr/bin/python # -*- coding: UTF-8 -*- -# Copyright 2012-2015 Luiko Czub, TestLink-API-Python-client developers +# Copyright 2012-2019 Luiko Czub, TestLink-API-Python-client developers # # Licensed under Apache 2.0 # @@ -17,10 +17,9 @@ 'Development Status :: 5 - Production/Stable', 'License :: OSI Approved :: Apache Software License', 'Operating System :: OS Independent', - 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3.3', - 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', 'Topic :: Software Development :: Testing', 'Topic :: Software Development :: Libraries :: Python Modules' ] @@ -87,4 +86,3 @@ keywords = ['testing', 'testlink', 'xml-rpc', 'testautomation'] ) - diff --git a/src/testlink/__init__.py b/src/testlink/__init__.py index b62ffef..7b5de11 100644 --- a/src/testlink/__init__.py +++ b/src/testlink/__init__.py @@ -1,7 +1,7 @@ #! /usr/bin/python # -*- coding: UTF-8 -*- -# Copyright 2012-2015 TestLink-API-Python-client developers +# Copyright 2012-2019 TestLink-API-Python-client developers # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -21,4 +21,6 @@ from .testlinkerrors import TestLinkError from .testlinkapigeneric import TestlinkAPIGeneric from .testlinkapi import TestlinkAPIClient -from .testlinkhelper import TestLinkHelper \ No newline at end of file +from .testlinkhelper import TestLinkHelper +from .testreporter import TestReporter +from .testgenreporter import TestGenReporter diff --git a/src/testlink/proxiedtransport.py b/src/testlink/proxiedtransport.py deleted file mode 100644 index 0b907ab..0000000 --- a/src/testlink/proxiedtransport.py +++ /dev/null @@ -1,106 +0,0 @@ -#! /usr/bin/python -# -*- coding: UTF-8 -*- - -# Copyright 2014-2015 Mario Benito, TestLink-API-Python-client developers -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# ------------------------------------------------------------------------ - -import sys -IS_PY3 = sys.version_info[0] > 2 -if IS_PY3: - from xmlrpc.client import Transport - from http.client import HTTPConnection -else: - from xmlrpclib import Transport - from httplib import HTTPConnection - -try: - import gzip -except ImportError: - gzip = None #python can be built without zlib/gzip support - - -class ProxiedTransport(Transport): - def __init__(self): - if IS_PY3: - super(ProxiedTransport, self).__init__() - else: - Transport.__init__(self) - self.realhost = None - self.proxy = None - - def set_proxy(self, proxy): - """Define HTTP proxy (with optional basic auth) - - :param str proxy: Proxy string - """ - cproxy, auth, x509 = self.get_host_info(proxy) - self.proxy = cproxy - if auth: - auth = [ ('Proxy-Authorization', auth[0][1]) ] - if self._extra_headers: - self._extra_headers.extend(auth) - else: - self._extra_headers = auth - - def make_connection(self, host): - """return an existing connection if possible. This allows HTTP/1.1 keep-alive. - - :param str|(str, {}) host: Host descriptor (URL or (URL, x509 info) tuple) - :return httplib.HTTPConnection: - """ - if self._connection and host == self._connection[0]: - return self._connection[1] - - # create a HTTP connection object from a host descriptor - chost, auth, x509 = self.get_host_info(host) - if auth: - if self._extra_headers: - self._extra_headers.extend(auth) - else: - self._extra_headers = auth - self.realhost = host - self._connection = host, HTTPConnection(self.proxy) - return self._connection[1] - - def send_request(self, connection, handler, request_body): - """Send request header - - :param httplib.HTTPConnection connection: Connection handle - :param str handler: Target RPC handler - :param str request_body:XML-RPC body - """ - if self.accept_gzip_encoding and gzip: - connection.putrequest("POST", 'http://%s%s' % (self.realhost, handler), skip_accept_encoding=True) - connection.putheader("Accept-Encoding", "gzip") - else: - connection.putrequest("POST", 'http://%s%s' % (self.realhost, handler)) - - def send_host(self, connection, host): - """Send host name - - Note: This function doesn't actually add the "Host" - header anymore, it is done as part of the connection.putrequest() in - send_request() above. - - :param httplib.HTTPConnection connection: Connection handle - :param str host: Host name - """ - extra_headers = self._extra_headers - if extra_headers: - if isinstance(extra_headers, dict()): - extra_headers = extra_headers.items() - for key, value in extra_headers: - connection.putheader(key, value) diff --git a/src/testlink/proxiedtransport2.py b/src/testlink/proxiedtransport2.py new file mode 100644 index 0000000..dd7bf2c --- /dev/null +++ b/src/testlink/proxiedtransport2.py @@ -0,0 +1,43 @@ +#! /usr/bin/python +# -*- coding: UTF-8 -*- + +# Copyright 2014-2020 Mario Benito, Luiko Czub, TestLink-API-Python-client developers +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------ +# XMLRPC ProxiedTransport for Py27 as described in +# https://docs.python.org/2.7/library/xmlrpclib.html#example-of-client-usage +# ------------------------------------------------------------------------ + +import xmlrpclib, httplib + +class ProxiedTransport(xmlrpclib.Transport): + ''' XMLRPC ProxiedTransport for Py27 as described in + https://docs.python.org/2.7/library/xmlrpclib.html#example-of-client-usage + ''' + + def set_proxy(self, proxy): + self.proxy = proxy + + def make_connection(self, host): + self.realhost = host + h = httplib.HTTPConnection(self.proxy) + return h + + def send_request(self, connection, handler, request_body): + connection.putrequest("POST", 'http://%s%s' % (self.realhost, handler)) + + def send_host(self, connection, host): + connection.putheader('Host', self.realhost) + diff --git a/src/testlink/proxiedtransport3.py b/src/testlink/proxiedtransport3.py new file mode 100644 index 0000000..e12ab77 --- /dev/null +++ b/src/testlink/proxiedtransport3.py @@ -0,0 +1,46 @@ +#! /usr/bin/python +# -*- coding: UTF-8 -*- + +# Copyright 2020 Luiko Czub, TestLink-API-Python-client developers +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------ + +from http.client import HTTPConnection +from xmlrpc.client import Transport +from urllib.parse import urlparse, urlunparse + +class ProxiedTransport(Transport): + ''' XMLRPC ProxiedTransport for Py37+ as described in + https://docs.python.org/3.8/library/xmlrpc.client.html#example-of-client-usage + ''' + + def set_proxy(self, host, port=None, headers=None): + ''' if host includes a port definition (e.g. http://myHost:1111) + this will be used instead the optional PORT arg + ''' + + u1 = urlparse(host) + uport = u1.port + u2 = u1._replace(netloc=u1.hostname) + uhost = urlunparse(u2) + + self.proxy = uhost, uport or port + self.proxy_headers = headers + + def make_connection(self, host): + connection = HTTPConnection(*self.proxy) + connection.set_tunnel(host, headers=self.proxy_headers) + self._connection = host, connection + return connection diff --git a/src/testlink/testgenreporter.py b/src/testlink/testgenreporter.py new file mode 100644 index 0000000..d856dd2 --- /dev/null +++ b/src/testlink/testgenreporter.py @@ -0,0 +1,50 @@ +#! /usr/bin/python +# -*- coding: UTF-8 -*- + +# Copyright 2017-2019 Brian-Willams, TestLink-API-Python-client developers +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------ + +from .testreporter import AddTestCaseReporter, AddBuildReporter, AddTestPlanReporter, AddPlatformReporter, TestReporter + + +class TestGenReporter(AddTestCaseReporter, AddBuildReporter, AddTestPlanReporter, AddPlatformReporter, TestReporter): + """ + This is the default generate everything it can version of test reporting. + + This class will always try to report a result. It will generate everything possible and will change with additional + Add*Reporter's added to the repo. As such you should only use this if you want to always generate everything this + repo is capable of. If you want what it does at any specific time you should create this class in your project and + use directly. + + If you don't want to generate one of these values you can 'roll your own' version of this class with only the + needed features that you want to generate. As stated above if you *only* want to generate what this class currently + does. Copying it into your project is the best practice as this class is mutable inside the project! + + For example if you wanted to add platforms and/or tests to testplans, but didn't want to ever make a new testplan + you could use a class like: + `type('MyOrgTestGenReporter', (AddTestCaseReporter, AddPlatformReporter, TestReporter), {})` + + Example usage with fake testlink server test and a manual project. + ``` + tls = testlink.TestLinkHelper('https://testlink.corp.com/testlink/lib/api/xmlrpc/v1/xmlrpc.php', + 'devkeyabc123').connect(testlink.TestlinkAPIClient) + tgr = TestGenReporter(tls, ['TEST-123'], testprojectname='MANUALLY_MADE_PROJECT', testplanname='generated', + platformname='gend', buildname='8.fake', status='p') + tgr.report() + ``` + + Attention - the list of test case IDs must use full external testcase IDs + """ diff --git a/src/testlink/testlinkapi.py b/src/testlink/testlinkapi.py index 5504298..0ca7e14 100644 --- a/src/testlink/testlinkapi.py +++ b/src/testlink/testlinkapi.py @@ -1,7 +1,7 @@ #! /usr/bin/python # -*- coding: UTF-8 -*- -# Copyright 2011-2015 Luiko Czub, Olivier Renault, James Stock, TestLink-API-Python-client developers +# Copyright 2011-2019 Luiko Czub, Olivier Renault, James Stock, TestLink-API-Python-client developers # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -21,7 +21,7 @@ from __future__ import print_function from .testlinkapigeneric import TestlinkAPIGeneric, TestLinkHelper -from .testlinkerrors import TLArgError +from .testlinkerrors import TLArgError, TLResponseError import sys @@ -58,7 +58,7 @@ def __init__(self, server_url, devKey, **kwargs): # reportTCResult(None, newTestPlanID, None, 'f', '', guess=True, # testcaseexternalid=tc_aa_full_ext_id) # otherwise xmlrpclib raise an error, that None values are not allowed - self.stepsList = [] + self._emptyStepsList() self._changePositionalArgConfig() def _changePositionalArgConfig(self): @@ -125,12 +125,23 @@ def createTestCase(self, *argsPositional, **argsOptional): """ createTestCase: Create a test case positional args: testcasename, testsuiteid, testprojectid, authorlogin, summary - optional args : preconditions, importance, executiontype, order, - internalid, checkduplicatedname, actiononduplicatedname + optional args : steps, preconditions, importance, executiontype, order, + internalid, checkduplicatedname, actiononduplicatedname, + status, estimatedexecduration argument 'steps' will be set with values from .stepsList, - when argsOptional does not include a 'steps' item - .stepsList can be filled before call via .initStep() and .appendStep() + + otherwise, optional arg 'steps' must be defined as a list with + dictionaries , example + [{'step_number' : 1, 'actions' : "action A" , + 'expected_results' : "result A", 'execution_type' : 0}, + {'step_number' : 2, 'actions' : "action B" , + 'expected_results' : "result B", 'execution_type' : 1}, + {'step_number' : 3, 'actions' : "action C" , + 'expected_results' : "result C", 'execution_type' : 0}] + """ # store current stepsList as argument 'steps', when argsOptional defines @@ -201,7 +212,23 @@ def copyTCnewTestCase(self, origTestCaseId, origVersion=None, **changedAttribute return self._copyTC(origTestCaseId, changedAttributes, origVersion, duplicateaction = 'generate_new') - + def getTestCaseByVersion(self, testCaseID, version=None): + """ + Gets testcase information based on the version. + + :param testCaseID: test case to search for + :param version: version to search for defaults to None. None searches for latest + :return: test case info dictionary + """ + testcases = self.getTestCase(testCaseID, version=version) + if version is None: + return testcases[0] + for testcase in testcases: + if str(testcase['version']) == str(version): + return testcase + else: + raise TLArgError("Testcase {} doesn't have version {}.".format(testCaseID, version)) + def _copyTC(self, origTestCaseId, changedArgs, origVersion=None, **options): """ creates a copy of test case with id ORIGTESTCASEID @@ -226,7 +253,7 @@ def _copyTC(self, origTestCaseId, changedArgs, origVersion=None, **options): """ # get orig test case content - origArgItems = self.getTestCase(origTestCaseId, version=origVersion)[0] + origArgItems = self.getTestCaseByVersion(origTestCaseId, version=origVersion) # get orig test case project id origArgItems['testprojectid'] = self.getProjectIDByNode(origTestCaseId) @@ -266,7 +293,8 @@ def _copyTCbuildArgs(self, origArgItems, changedArgs, options): externalArgNames.extend(optArgNames) externalTointernalNames = {'testcasename' : 'name', 'testsuiteid' : 'testsuite_id', 'authorlogin' : 'author_login', - 'executiontype' : 'execution_type', 'order' : 'node_order'} + 'executiontype' : 'execution_type', 'order' : 'node_order', + 'estimatedexecduration' : 'estimated_exec_duration' } # extend origItems with some values needed in createTestCase origArgItems['checkduplicatedname'] = 1 @@ -457,14 +485,8 @@ def initStep(self, actions, expected_results, execution_type): """ initStep : Initializes the list which stores the Steps of a Test Case to create """ - self.stepsList = [] - lst = {} - lst['step_number'] = '1' - lst['actions'] = actions - lst['expected_results'] = expected_results - lst['execution_type'] = str(execution_type) - self.stepsList.append(lst) - return True + self._emptyStepsList() + return self.appendStep(actions, expected_results, execution_type) def appendStep(self, actions, expected_results, execution_type): """ appendStep : @@ -476,17 +498,71 @@ def appendStep(self, actions, expected_results, execution_type): lst['expected_results'] = expected_results lst['execution_type'] = str(execution_type) self.stepsList.append(lst) - return True + return True + + def _emptyStepsList(self): + """ reset .stepsList to an empty List """ + self.stepsList = [] - def getProjectIDByName(self, projectName): - projects=self.getProjects() - result=-1 - for project in projects: - if (project['name'] == projectName): - result = project['id'] - break - return result + def getProjectIDByName(self, projectName): + try: + project=self.getTestProjectByName(projectName) + userID = project['id'] + except KeyError: + userID = -1 + + return userID + + def ensureUserExist(self, login, **userArgs): + """ combines getUserByLogin() + createUser() + creates new user only, when login not exist + + returns userID + + userArgs defines optional key value pairs used to create new user + - firstname Default 'unknown' + - lastname Default 'via pyTLapi' + - email Default 'unknown@example.com' + - password Default None + """ + + try: + response = self.getUserByLogin(login) + userID = response[0]['dbID'] + except TLResponseError as tl_err: + if tl_err.code == 10000: + # Cannot Find User Login create new user + name1 = userArgs.get('firstname', 'unknown') + name2 = userArgs.get('lastname', 'via pyTLapi') + mail = userArgs.get('email', 'unknown@example.com') + pw = userArgs.get('password') + userID = self.createUser(login, name1, name2, mail, password=pw) + else: + # seems to be another response failure - we forward it + raise + + return userID + def ensureUserExistWithProjectRole(self, login, rolename, projectname, **userArgs): + """ combines ensureUserExist() + setUserRoleOnProject() + creates new user only, when login not exist + + returns list with users account details + + rolename + - e.g. tester, test designer, senior tester, guest ... + + userArgs defines optional key value pairs used to create new user + - firstname Default 'unknown' + - lastname Default 'via pyTLapi' + - email Default 'unknown@example.com' + - password Default None + """ + + userID = self.ensureUserExist(login, **userArgs) + projectID = self.getProjectIDByName(projectname) + self.setUserRoleOnProject(userID, rolename, projectID) + return self.getUserByID(userID) if __name__ == "__main__": tl_helper = TestLinkHelper() diff --git a/src/testlink/testlinkapigeneric.py b/src/testlink/testlinkapigeneric.py index 4a670e8..a534fd4 100644 --- a/src/testlink/testlinkapigeneric.py +++ b/src/testlink/testlinkapigeneric.py @@ -1,7 +1,7 @@ #! /usr/bin/python # -*- coding: UTF-8 -*- -# Copyright 2013-2015 Luiko Czub, TestLink-API-Python-client developers +# Copyright 2013-2019 Luiko Czub, TestLink-API-Python-client developers # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -18,16 +18,18 @@ # ------------------------------------------------------------------------ import sys, os.path -IS_PY3 = sys.version_info[0] < 3 -if IS_PY3: - import xmlrpclib +IS_PY3 = sys.version_info[0] > 2 +if not IS_PY3: + # importing required py2 modules + import xmlrpclib as xmlrpc_client # in py 3 encodestring is deprecated and an alias for encodebytes # see issue #39 and compare py2 py3 doc # https://docs.python.org/2/library/base64.html#base64.encodestring # https://docs.python.org/3/library/base64.html#base64.encodebytes from base64 import encodestring as encodebytes else: - import xmlrpc.client as xmlrpclib + # importing required py3 modules + import xmlrpc.client as xmlrpc_client from base64 import encodebytes from platform import python_version @@ -41,7 +43,6 @@ decoMakerApiCallReplaceTLResponseError, decoMakerApiCallWithArgs, \ decoMakerApiCallChangePosToOptArg - class TestlinkAPIGeneric(object): """ client for XML-RPC communication between Python and TestLink Implements the TestLink API methods as generic PY methods with @@ -63,10 +64,13 @@ class TestlinkAPIGeneric(object): def __init__(self, server_url, devKey, **args): transport=args.get('transport') encoding=args.get('encoding') - verbose=args.get('verbose',0) - allow_none=args.get('allow_none',0) - self.server = xmlrpclib.Server(server_url, transport, encoding, - verbose, allow_none) + verbose=args.get('verbose',False) + allow_none=args.get('allow_none',False) + use_datetime = args.get('use_datetime', False) + context = args.get('context', None) + self.server = xmlrpc_client.ServerProxy(server_url, transport, encoding, + verbose, allow_none, use_datetime, + context=context) self.devKey = devKey self._server_url = server_url self._positionalArgNames = getMethodsWithPositionalArgs() @@ -147,12 +151,50 @@ def repeat(self): def about(self): """ Gives basic information about the API """ +# /** +# * Creates a new build for a specific test plan +# * +# * @param struct $args +# * @param string $args["devKey"] +# * @param int $args["testplanid"] +# * @param string $args["buildname"]; +# * @param string $args["buildnotes"]; +# * @param string $args["active"]; +# * @param string $args["open"]; +# * @param string $args["releasedate"]: YYYY-MM-DD; +# * @param int $args["copytestersfrombuild"] OPTIONAL, +# * if > 0 and valid buildid tester assignments will be copied. +# * +# * @return mixed $resultInfo +# * +# * @access public +# */ +# public function createBuild($args) + @decoApiCallAddDevKey @decoMakerApiCallWithArgs(['testplanid', 'buildname'], - ['buildnotes']) + ['buildnotes', 'active', 'open', 'releasedate', + 'copytestersfrombuild']) def createBuild(self): - """ Creates a new build for a specific test plan """ + """ Creates a new build for a specific test plan + active : 1 (default) = activ 0 = inactiv + open : 1 (default) = open 0 = closed + releasedate : YYYY-MM-DD + copytestersfrombuild : valid buildid tester assignments will be copied. + """ + + @decoMakerApiCallReplaceTLResponseError() + @decoApiCallAddDevKey + @decoMakerApiCallWithArgs(['buildid']) + def closeBuild(self): + """ Close build + + buildid - ATTENTION must be an integer + - createBuild returns the id as a string + - convert it with int() before calling closeBuild() + """ + @decoMakerApiCallReplaceTLResponseError() @decoApiCallAddDevKey @decoMakerApiCallWithArgs() @@ -185,9 +227,32 @@ def getTestSuitesForTestPlan(self): returns an empty list, if no suite is assigned """ + +# /** +# * create a test project +# * +# * @param struct $args +# * @param string $args["devKey"] +# * @param string $args["testprojectname"] +# * @param string $args["testcaseprefix"] +# * @param string $args["notes"] OPTIONAL +# * @param map $args["options"] OPTIONAL ALL int treated as boolean +# * keys requirementsEnabled,testPriorityEnabled,automationEnabled,inventoryEnabled +# * +# * @param int $args["active"] OPTIONAL +# * @param int $args["public"] OPTIONAL +# * @param string $args["itsname"] OPTIONAL +# * @param boolean $args["itsEnabled"] OPTIONAL +# * +# * +# * @return mixed $resultInfo +# */ +# public function createTestProject($args) + @decoApiCallAddDevKey @decoMakerApiCallWithArgs(['testprojectname', 'testcaseprefix'], - ['notes', 'active', 'public', 'options']) + ['notes', 'active', 'public', 'options', + 'itsname', 'itsenabled']) def createTestProject(self): """ Create a test project @@ -201,7 +266,7 @@ def createTestProject(self): @decoMakerApiCallWithArgs(['testsuiteid'], ['deep', 'details', 'getkeywords']) def getTestCasesForTestSuite(self): - """ List test suites within a test plan alphabetically + """ List test cases within a test suite alphabetically details - default is 'simple', use 'full' if you want to get summary,steps & expected_results @@ -228,11 +293,50 @@ def getTestCaseIDByName(self): server return can be a list or a dictionary - optional arg testprojectname seems to create a dictionary response """ +# /** +# * createTestCase +# * @param struct $args +# * @param string $args["devKey"] +# * @param string $args["testcasename"] +# * @param int $args["testsuiteid"]: test case parent test suite id +# * @param int $args["testprojectid"]: test case parent test suite id +# * +# * @param string $args["authorlogin"]: to set test case author +# * @param string $args["summary"] +# * @param array $args["steps"] +# * +# * @param string $args["preconditions"] - optional +# * @param int $args["importance"] - optional - see const.inc.php for domain +# * @param int $args["execution"] - optional - see ... for domain +# * @param int $args["order'] - optional +# * @param int $args["internalid"] - optional - do not use +# * @param string $args["checkduplicatedname"] - optional +# * @param string $args["actiononduplicatedname"] - optional +# * @param int $args["status"] - optional - see const.inc.php $tlCfg->testCaseStatus +# * @param number $args["estimatedexecduration"] - optional +# * +# * @return mixed $resultInfo +# * @return string $resultInfo['operation'] - verbose operation +# * @return boolean $resultInfo['status'] - verbose operation +# * @return int $resultInfo['id'] - test case internal ID (Database ID) +# * @return mixed $resultInfo['additionalInfo'] +# * @return int $resultInfo['additionalInfo']['id'] same as $resultInfo['id'] +# * @return int $resultInfo['additionalInfo']['external_id'] without prefix +# * @return int $resultInfo['additionalInfo']['status_ok'] 1/0 +# * @return string $resultInfo['additionalInfo']['msg'] - for debug +# * @return string $resultInfo['additionalInfo']['new_name'] only present if new name generation was needed +# * @return int $resultInfo['additionalInfo']['version_number'] +# * @return boolean $resultInfo['additionalInfo']['has_duplicate'] - for debug +# * @return string $resultInfo['message'] operation message +# */ +# public function createTestCase($args) + @decoApiCallAddDevKey @decoMakerApiCallWithArgs(['testcasename', 'testsuiteid', 'testprojectid', 'authorlogin', 'summary', 'steps'], ['preconditions', 'importance', 'executiontype', 'order', - 'internalid', 'checkduplicatedname', 'actiononduplicatedname']) + 'internalid', 'checkduplicatedname', 'actiononduplicatedname', + 'status', 'estimatedexecduration']) def createTestCase(self): """ Create a test case @@ -243,14 +347,93 @@ def createTestCase(self): 'expected_results' : "result B", 'execution_type' : 1}, {'step_number' : 3, 'actions' : "action C" , 'expected_results' : "result C", 'execution_type' : 0}] + + possible values for optional arguments testlink/cfg/const.inc.php + importance: 1 (low) 2 (medium) 3 (high) + status: 1 (draft) 2 (readyForReview) + 3 (reviewInProgress) 4 (rework) + 5 (obsolete) 6 (future) + 7 (final) + executiontype: 1 (Manual) 2 (Automated) """ +# /** +# * Reports a result for a single test case +# * +# * @param struct $args +# * @param string $args["devKey"] +# * @param int $args["testcaseid"]: optional, if not present +# * testcaseexternalid must be present +# * +# * @param int $args["testcaseexternalid"]: optional, if does not is present +# * testcaseid must be present +# * +# * +# * +# * @param int $args["testplanid"] +# * @param string $args["status"] - status is {@link $validStatusList} +# * @param int $args["buildid"] - optional. +# * if not present and $args["buildname"] exists +# * then +# * $args["buildname"] will be checked and used if valid +# * else +# * build with HIGHEST ID will be used +# * +# * @param int $args["buildname"] - optional. +# * if not present Build with higher internal ID will be used +# * +# * +# * @param string $args["notes"] - optional +# * @param string $args["execduration"] - optional +# * +# * @param bool $args["guess"] - optional defining whether to guess optinal params or require them +# * explicitly default is true (guess by default) +# * +# * @param string $args["bugid"] - optional +# * +# * @param string $args["platformid"] - optional, if not present platformname must be present +# * @param string $args["platformname"] - optional, if not present platformid must be present +# * +# * +# * @param string $args["customfields"] - optional +# * contains an map with key:Custom Field Name, value: value for CF. +# * VERY IMPORTANT: value must be formatted in the way it's written to db, +# * this is important for types like: +# * +# * DATE: strtotime() +# * DATETIME: mktime() +# * MULTISELECTION LIST / CHECKBOX / RADIO: se multipli selezione ! come separatore +# * +# * +# * these custom fields must be configured to be writte during execution. +# * If custom field do not meet condition value will not be written +# * +# * @param boolean $args["overwrite"] - optional, if present and true, then last execution +# * for (testcase,testplan,build,platform) will be overwritten. +# * +# * @param boolean $args["user"] - optional, if present and user is a valid login +# * (no other check will be done) it will be used when writting execution. +# * +# * @param string $args["timestamp"] - optional, if not present now is used +# * format YYYY-MM-DD HH:MM:SS +# * example 2015-05-22 12:15:45 +# * @return mixed $resultInfo +# * [status] => true/false of success +# * [id] => result id or error code +# * [message] => optional message for error message string +# * @access public +# * +# * @internal revisions +# * +# */ +# public function reportTCResult($args) + @decoApiCallAddDevKey @decoMakerApiCallWithArgs(['testplanid', 'status'], ['testcaseid', 'testcaseexternalid', 'buildid', 'buildname', 'platformid', 'platformname', 'notes', 'guess', 'bugid', 'customfields', 'overwrite', 'user', 'execduration', - 'timestamp']) + 'timestamp', 'steps']) def reportTCResult(self): """ Reports a result for a single test case @@ -263,9 +446,11 @@ def reportTCResult(self): overwrite : if present and true, then last execution for (testcase,testplan,build,platform) will be overwritten. user : if present and user is a valid login (no other check will be done) - it will be used when writting execution. + it will be used when writing execution. execduration : Exec (min) as float (2.5 = 2min 30sec) - timestamp : 'YYYY-MM-DD hh:mm[:ss]' + timestamp : 'YYYY-MM-DD hh:mm[:ss]'# + steps : [{'step_number' : 6, 'result' : 'p', 'notes" : 'a_note'}, + {'step_number' : 7, 'result' : 'f', 'notes" : 'blabla'}] """ # /** @@ -314,6 +499,8 @@ def reportTCResult(self): # * 'full': (default) get summary,steps,expected_results,test suite name # * 'simple': # * 'details': +# * @param array $args["customfields"] +# * - optional can be a boolean or an array with the requested fields # * @return mixed $resultInfo # * # * @internal revisions @@ -327,7 +514,8 @@ def reportTCResult(self): @decoMakerApiCallWithArgs(['testplanid'], ['buildid', 'platformid', 'testcaseid', 'keywordid', 'keywords', 'executed', 'assignedto', - 'executestatus', 'executiontype', 'getstepinfo', 'details']) + 'executestatus', 'executiontype', 'getstepinfo', 'details', + 'customfields']) def getTestCasesForTestPlan(self): """ List test cases linked to a test plan @@ -365,6 +553,7 @@ def getTestCaseCustomFieldDesignValue(self): # * @param args['platformid'] - OPTIONAL Only if test plan has no platforms # * @param args['executionorder'] - OPTIONAL # * @param args['urgency'] - OPTIONAL +# * @param args['overwrite'] - OPTIONAL # * # */ # public function addTestCaseToTestPlan($args) @@ -372,7 +561,7 @@ def getTestCaseCustomFieldDesignValue(self): @decoApiCallAddDevKey @decoMakerApiCallWithArgs(['testprojectid', 'testplanid', 'testcaseexternalid', 'version'], - ['platformid', 'executionorder', 'urgency']) + ['platformid', 'executionorder', 'urgency', 'overwrite']) def addTestCaseToTestPlan(self): """ Add a test case version to a test plan """ @@ -418,10 +607,40 @@ def assignRequirements(self): b) requirements with ID 6735 and 6737 of requirement spec 6733 """ +# /** +# * Gets attachments for specified test case VERSION. +# * The attachment file content is Base64 encoded. To save the file to disk in client, +# * Base64 decode the content and write file in binary mode. +# * +# * @param struct $args +# * @param string $args["devKey"] +# * Developer key +# * @param int $args["testcaseid"]: +# * optional, if does not is present +# * testcaseexternalid must be present +# * +# * @param int $args["version"]: +# * optional, if not present, the latest version will be used +# * +# * @param int $args["testcaseexternalid"]: +# * optional, if does not is present +# * testcaseid must be present +# * +# * @return mixed $resultInfo +# */ +# public function getTestCaseAttachments($args) { + @decoApiCallAddDevKey - @decoMakerApiCallWithArgs([], ['testcaseid', 'testcaseexternalid']) + @decoMakerApiCallWithArgs([], ['testcaseid', 'version', + 'testcaseexternalid']) def getTestCaseAttachments(self): """ Gets attachments for specified test case. + + args variations: testcaseid - testcaseexternalid + + version - optional, if not present, the latest test case version + will be used + The attachment file content is Base64 encoded. To save the file to disk in client, Base64 decode the content and write file in binary mode. """ @@ -500,6 +719,10 @@ def getFullPath(self): def deleteExecution(self): """ delete an execution + executionid - ATTENTION must be an integer + - reportTCResult returns the id as a string + - convert it with int() before calling deleteExecution() + Default TL server configuration does not allow deletion of exections see Installation & Configuration Manual Version 1.9 chap. 5.8. Test execution settings @@ -631,14 +854,44 @@ def uploadTestSuiteAttachment(self): ATTACHMENTFILE, but user could overwrite it, if user want to store the attachment with a different name """ +# /** +# * Uploads an attachment for a Test Case. +# * +# * The attachment content must be Base64 encoded by the client before sending it. +# * +# * @param struct $args +# * @param string $args["devKey"] +# * Developer key +# * @param int $args["testcaseid"] +# * Test Case INTERNAL ID +# * @param int $args["version"] +# * version number +# * +# * @param string $args["title"] +# * (Optional) The title of the Attachment +# * @param string $args["description"] +# * (Optional) The description of the Attachment +# * @param string $args["filename"] +# * The file name of the Attachment(e.g.:notes.txt) +# * @param string $args["filetype"] +# * The file type of the Attachment(e.g.: text/plain) +# * @param string $args["content"] +# * The content(Base64 encoded) of the Attachment +# * +# * @return mixed $resultInfo an array containing the fk_id, fk_table, title, +# * description, file_name, file_size and file_type. If any errors occur it +# * returns the erros map. +# */ +# public function uploadTestCaseAttachment($args) { @decoApiCallAddAttachment - @decoMakerApiCallWithArgs(['testcaseid'], + @decoMakerApiCallWithArgs(['testcaseid', 'version'], ['title', 'description', 'filename', 'filetype', 'content']) def uploadTestCaseAttachment(self): """ Uploads an attachment for a Test Case. testcaseid - Test Case INTERNAL ID + version - Test Case version number mandatory non api args: attachmentfile - python file descriptor pointing to the file @@ -961,10 +1214,25 @@ def setTestCaseExecutionType(self): @decoMakerApiCallWithArgs(['testplanid']) def getExecCountersByBuild(self): """ Gets execution metrics information for a testplan """ - + # /** + # * create platform + # * + # * @param struct $args + # * @param string $args["devKey"] + # * @param string $args["testprojectname"] + # * @param string $args["platformname"] + # * @param string $args["notes"] + # * @param boolean $args["platformondesign"] + # * @param boolean $args["platformonexecution"] + # * @return mixed $resultInfo + # * @internal revisions + # */ + # public function createPlatform($args) { + @decoApiCallAddDevKey @decoMakerApiCallWithArgs(['testprojectname', 'platformname'], - ['notes']) + ['notes', + 'platformondesign', 'platformonexecution']) def createPlatform(self): """ Creates a platform for test project """ @@ -1071,6 +1339,31 @@ def getUserByID(self): * ATTENTION: userApiKey will be set to NULL, because is worst that access to user password """ +# /** +# * Update an existing test case +# * Not all test case attributes will be able to be updated using this method +# * See details below +# * +# * @param struct $args +# * @param string $args["devKey"] +# * @param string $args["testcaseexternalid"] format PREFIX-NUMBER +# * @param int $args["version"] optional version NUMBER (human readable) +# * @param string $args["testcasename"] - optional +# * @param string $args["summary"] - optional +# * @param string $args["preconditions"] - optional +# * @param array $args["steps"] - optional +# * each element is a hash with following keys +# * step_number,actions,expected_results,execution_type +# * +# * @param int $args["importance"] - optional - see const.inc.php for domain +# * @param int $args["executiontype"] - optional - see ... for domain +# * @param int $args["status'] - optional +# * @param int $args["estimatedexecduration'] - optional +# * @param string $args["user'] - login name used as updater - optional +# * if not provided will be set to user that request update +# */ +# public function updateTestCase($args) + @decoApiCallAddDevKey @decoMakerApiCallWithArgs(['testcaseexternalid'], ['version', 'testcasename','summary', 'preconditions', 'steps', @@ -1165,7 +1458,7 @@ def getTestCaseBugs(self): testplanid test plan id - args variations: testcaseid - testcaseexternalid (mandatoy!) + args variations: testcaseid - testcaseexternalid (mandatory!) buildid - buildname platformid - platformname test case information is general mandatory @@ -1442,8 +1735,384 @@ def deleteTestProject(self): def updateTestSuiteCustomFieldDesignValue(self): """ Update value of Custom Field with scope='design' for a given Test Suite - customfields : dictionary with customfields names + values - VERY IMPORTANT: value must be formatted in the way it's written to db + customfields : dictionary with customfields names + values + VERY IMPORTANT: value must be formatted in the way it's written to db + """ + +# /** +# * Returns all test suites inside target +# * test project with target name +# * +# * @param +# * @param struct $args +# * @param string $args["devKey"] +# * @param int $args["testsuitename"] +# * @param string $args["prefix"] +# * @return mixed $resultInfo +# * +# * @access public +# */ +# public function getTestSuite($args) + + @decoApiCallAddDevKey + @decoMakerApiCallWithArgs(['testsuitename', 'prefix']) + def getTestSuite(self): + """ Returns list with all test suites named TESTUITENAME defined for + test project using PREFIX """ + + +# /** +# * update a test suite +# * +# * @param struct $args +# * @param string $args["devKey"] +# * @param int $args["testprojectid"] OR string $args["prefix"] +# * @param string $args["testsuitename"] optional +# * @param string $args["details"] optional +# * @param int $args["parentid"] optional, if do not provided means test suite must be top level. +# * @param int $args["order"] optional. Order inside parent container +# * +# * @return mixed $resultInfo +# */ +# public function updateTestSuite($args) + + @decoApiCallAddDevKey + @decoMakerApiCallWithArgs(['testsuiteid'], + ['testprojectid', 'prefix', 'parentid', 'testsuitename', 'details', + 'order']) + def updateTestSuite(self): + """ update a test suite + + mandatory arg: testsuiteid - identifies the test suite to be change + + mandatory args variations: testprojectid or prefix + - test project information is general mandatory + + optional args: + - testsuitename - if defined, test suite name will be changed + - details - if defined test suite details will be changed + - order - if defined, order inside parent container is changed + """ + +# /** +# * Get Issue Tracker System by name +# * +# * @param struct $args +# * @param string $args["devKey"] +# * @param string $args["itsname"] ITS name +# * @return mixed $itsObject +# * @access public +# */ +# public function getIssueTrackerSystem($args,$call=null) + + @decoApiCallAddDevKey + @decoMakerApiCallWithArgs(['itsname'], []) + def getIssueTrackerSystem(self): + """ Get Issue Tracker System by name """ + +# /** +# * Update value of Custom Field with scope='design' +# * for a given Build +# * +# * @param struct $args +# * @param string $args["devKey"]: used to check if operation can be done. +# * if devKey is not valid => abort. +# * +# * @param string $args["buildid"]: +# * @param string $args["testprojectid"]: +# * @param string $args["customfields"] +# * contains an map with key:Custom Field Name, value: value for CF. +# * VERY IMPORTANT: value must be formatted in the way it's written to db, +# * this is important for types like: +# * +# * DATE: strtotime() +# * DATETIME: mktime() +# * MULTISELECTION LIST / CHECKBOX / RADIO: se multipli selezione ! come separatore +# * +# * +# * these custom fields must be configured to be writte during execution. +# * If custom field do not meet condition value will not be written +# * +# * @return mixed null if everything ok, else array of IXR_Error objects +# * +# * @access public +# */ +# public function updateBuildCustomFieldsValues($args) + + @decoApiCallAddDevKey + @decoMakerApiCallWithArgs(['testprojectid', 'testplanid', 'buildid', + 'customfields']) + def updateBuildCustomFieldsValues(self): + """ Update value of Custom Field with scope='design' for a given Build + + customfields : dictionary with customfields names + values + VERY IMPORTANT: value must be formatted in the way it's written to db + """ + +# /** +# * Gets a set of EXECUTIONS for a particular testcase on a test plan. +# * If there are no filter criteria regarding platform and build, +# * result will be get WITHOUT checking for a particular platform and build. +# * +# * @param struct $args +# * @param string $args["devKey"] +# * @param int $args["tplanid"] +# * @param int $args["testcaseid"]: Pseudo optional. +# * if is not present then testcaseexternalid MUST BE present +# * +# * @param int $args["testcaseexternalid"]: Pseudo optional. +# * if is not present then testcaseid MUST BE present +# * +# * @param string $args["platformid"]: optional. +# * ONLY if not present, then $args["platformname"] +# * will be analized (if exists) +# * +# * @param string $args["platformname"]: optional (see $args["platformid"]) +# * @param int $args["buildid"]: optional +# * ONLY if not present, $args["buildname"] will be analized (if exists) +# * +# * @param int $args["buildname"] - optional (see $args["buildid"]) +# * @param int $args["options"] - optional +# * options['getOrderDescending'] +# * false(=ascending,default) +# * @return mixed $resultInfo +# * if execution found +# * array that contains a map with these keys: +# * id (execution id),build_id,tester_id,execution_ts, +# * status,testplan_id,tcversion_id,tcversion_number, +# * execution_type,notes. +# * +# * if test case has not been executed, +# * array('id' => -1) +# * @access public +# */ +# public function getExecutionSet($args) + + @decoApiCallAddDevKey + @decoMakerApiCallWithArgs(['testplanid'], + ['testcaseid', 'testcaseexternalid', + 'buildid', 'buildname', 'platformid', 'platformname', + 'options']) + def getExecutionSet(self): + """ Gets a set of EXECUTIONS for a particular testcase on a test plan. + If there are no filter criteria regarding platform and build, result + will be get WITHOUT checking for a particular platform and build. + + mandatory arg: testplanid - identifies the test plan + + mandatory args variations: testcaseid - testcaseexternalid + - test case information is general mandatory + + optional args variations: buildid - buildname + platformid - platformname + + options : dictionary with key 'getOrderDescending' and + values 0 (false = default) or 1 (true) + """ + +# /** +# * Get requirements +# * +# * @param string $args["testprojectid"] +# * @param string $args["testplanid"] OPTIONAL +# * @param string $args["platformid"] OPTIONAL +# * +# * @return mixed error if someting's wrong, else array of test cases +# * +# * @access public +# */ +# public function getRequirements($args) + + @decoMakerApiCallReplaceTLResponseError() + @decoApiCallAddDevKey + @decoMakerApiCallWithArgs(['testprojectid'], ['testplanid', 'platformid']) + def getRequirements(self): + """ Get requirements. + + mandatory arg: testprojectid - identifies the test project + + optional args: testplanid, platformid + """ + +# /** +# * Get requirement coverage +# * +# * Retrieve the test cases associated to a requirement +# * +# * @param struct $args +# * @param string $args["devKey"]: used to check if operation can be done. +# * if devKey is not valid => abort. +# * +# * @param string $args["testprojectid"] +# * @param string $args["requirementdocid"] +# * +# * @return mixed error if someting's wrong, else array of test cases +# * +# * @access public +# */ +# public function getReqCoverage($args) + + @decoMakerApiCallReplaceTLResponseError() + @decoApiCallAddDevKey + @decoMakerApiCallWithArgs(['testprojectid', 'requirementdocid'], []) + def getReqCoverage(self): + """ Get requirement coverage. + Retrieve the test cases associated to a requirement + + mandatory arg: + testprojectid - identifies the test project + requirementdocid - identifies the requirement + + """ + +# /** +# * +# * @param struct $args +# * @param string $args["devKey"] +# * @param string $args["testcaseexternalid"] format PREFIX-NUMBER +# * @param int $args["testsuiteid"] +# * +# */ +# public function setTestCaseTestSuite($args) + + @decoApiCallAddDevKey + @decoMakerApiCallWithArgs(['testcaseexternalid', 'testsuiteid'], []) + def setTestCaseTestSuite(self): + """ move a test case to a different Test Suite + + mandatory arg: + testcaseexternalid - identifies the test case + testsuiteid - identifies the test suite + + """ + +# /** +# * Gets attachments for specified test suite. +# * The attachment file content is Base64 encoded. To save the file to disk in client, +# * Base64 decode the content and write file in binary mode. +# * +# * @param struct $args +# * @param string $args["devKey"] Developer key +# * @param int $args["testsuiteid"]: id of the testsuite +# * +# * @return mixed $resultInfo +# * @author dennis@etern-it.de +# */ +# public function getTestSuiteAttachments($args) + + @decoApiCallAddDevKey + @decoMakerApiCallWithArgs(['testsuiteid'], []) + def getTestSuiteAttachments(self): + """ Gets attachments for specified test suite. + The attachment file content is Base64 encoded. To save the file to disk + in client, Base64 decode the content and write file in binary mode. """ + +# /** +# * Gets ALL EXECUTIONS for a particular testcase on a test plan. +# * If there are no filter criteria regarding platform and build, +# * result will be get WITHOUT checking for a particular platform and build. +# * +# * @param struct $args +# * @param string $args["devKey"] +# * @param int $args["tplanid"] +# * @param int $args["testcaseid"]: Pseudo optional. +# * if does not is present then testcaseexternalid MUST BE present +# * +# * @param int $args["testcaseexternalid"]: Pseudo optional. +# * if does not is present then testcaseid MUST BE present +# * +# * @param string $args["platform_id"]: optional. +# * will be analized ONLY if present and exists +# * +# * @param int $args["build_id"]: optional +# * will be analized ONLY if present and exists +# * +# * @param int $args["options"] - optional +# * options['getBugs'] = true / false +# * +# * +# * @return mixed $resultInfo +# * if executions found +# * array that contains a map for each execution with these keys: +# * id (execution id),build_id,tester_id,execution_ts, +# * status,testplan_id,tcversion_id,tcversion_number, +# * execution_type,notes. +# * +# * If user has requested getbugs, then a key bugs (that is an array) +# * will also exists. +# * +# * if test case has not been execute, the first map will be returned with -1 as 'id' +# * +# * @access public +# */ +# public function getAllExecutionsResults($args) + + @decoApiCallAddDevKey + @decoMakerApiCallWithArgs(['testplanid'], + ['testcaseid', 'testcaseexternalid', + 'platformid', 'buildid', 'options']) + def getAllExecutionsResults(self): + """ Gets ALL EXECUTIONS for a particular testcase on a test plan. + If there are no filter criteria regarding platform and build, + result will be get WITHOUT checking for a particular platform and build. + + mandatory arg: testplanid - identifies the test plan + + mandatory args variations: testcaseid - testcaseexternalid + - test case information is general mandatory + + optional args: buildid + platformid + + options : dictionary with key 'getBugs' and + values 0 (false = default) or 1 (true) + """ + + # /** + # * Create a new user + # * + # * Restricted to site admin + # * + # * @param struct $args + # * @param string $args["devKey"] + # * @param string $args["login"] + # * @param string $args["firstname"] + # * @param string $args["lastname"] + # * @param string $args["email"] + # * @param string $args["password"] - OPTIONAL + # * + # * + # * @return ID the new user if OK, otherwise error structure + # * + # * @access public + # */ + # public function createUser($args) { + @decoApiCallAddDevKey + @decoMakerApiCallWithArgs(['login', 'firstname', 'lastname', 'email'], + ['password']) + def createUser(self): + """ Create a new user """ + + # /** + # * Set a role to a user at project level + # * + # * Restricted to users with System Wide Role Admin + # * + # * @param struct $args + # * @param struct $args["userid"] + # * @param struct $args["rolename"] + # * @param struct $args["testprojectid"] + # * + # * @return true if OK, otherwise error structure + # * + # * @access public + # */ + # public function setUserRoleOnProject($args) + @decoApiCallAddDevKey + @decoMakerApiCallWithArgs(['userid', 'rolename', 'testprojectid']) + def setUserRoleOnProject(self): + """ Set a role to a user at project level + Restricted to users with System Wide Role Admin """ # @@ -1461,11 +2130,11 @@ def _callServer(self, methodNameAPI, argsAPI=None): response = getattr(self.server.tl, methodNameAPI)() else: response = getattr(self.server.tl, methodNameAPI)(argsAPI) - except (IOError, xmlrpclib.ProtocolError) as msg: + except (IOError, xmlrpc_client.ProtocolError) as msg: new_msg = 'problems connecting the TestLink Server %s\n%s' %\ (self._server_url, msg) raise testlinkerrors.TLConnectionError(new_msg) - except xmlrpclib.Fault as msg: + except xmlrpc_client.Fault as msg: new_msg = 'problems calling the API method %s\n%s' %\ (methodNameAPI, msg) raise testlinkerrors.TLAPIError(new_msg) @@ -1529,7 +2198,10 @@ def _getAttachmentArgs(self, attachmentfile): else: raise testlinkerrors.TLArgError( 'invalid attachment file: %s' % attachmentfile) - + finally: + # ensure file open inside this method call is closed again + if not already_file_obj: + a_file_obj.close() return {'filename':os.path.basename(a_file_path), 'filetype':guess_type(a_file_path)[0], diff --git a/src/testlink/testlinkargs.py b/src/testlink/testlinkargs.py index c03a2c9..bf40bb7 100644 --- a/src/testlink/testlinkargs.py +++ b/src/testlink/testlinkargs.py @@ -1,7 +1,7 @@ #! /usr/bin/python # -*- coding: UTF-8 -*- -# Copyright 2013-2015 Luiko Czub, TestLink-API-Python-client developers +# Copyright 2013-2019 Luiko Czub, TestLink-API-Python-client developers # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/testlink/testlinkdecorators.py b/src/testlink/testlinkdecorators.py index 1dd5c88..0027c06 100644 --- a/src/testlink/testlinkdecorators.py +++ b/src/testlink/testlinkdecorators.py @@ -1,7 +1,7 @@ #! /usr/bin/python # -*- coding: UTF-8 -*- -# Copyright 2013-2015 Luiko Czub, TestLink-API-Python-client developers +# Copyright 2013-2019 Luiko Czub, TestLink-API-Python-client developers # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/testlink/testlinkerrors.py b/src/testlink/testlinkerrors.py index 4176c1b..1795d8b 100644 --- a/src/testlink/testlinkerrors.py +++ b/src/testlink/testlinkerrors.py @@ -1,7 +1,7 @@ #! /usr/bin/python # -*- coding: UTF-8 -*- -# Copyright 2012-2015 Patrick Dassier, Luiko Czub, TestLink-API-Python-client developers +# Copyright 2012-2019 Patrick Dassier, Luiko Czub, TestLink-API-Python-client developers # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/testlink/testlinkhelper.py b/src/testlink/testlinkhelper.py index c222b78..fb866cc 100644 --- a/src/testlink/testlinkhelper.py +++ b/src/testlink/testlinkhelper.py @@ -1,7 +1,7 @@ #! /usr/bin/python # -*- coding: UTF-8 -*- -# Copyright 2012-2015 Luiko Czub, TestLink-API-Python-client developers +# Copyright 2012-2020 Luiko Czub, TestLink-API-Python-client developers # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -17,10 +17,16 @@ # # ------------------------------------------------------------------------ -import os +import os, sys from argparse import ArgumentParser from .version import VERSION +import ssl +IS_PY3 = sys.version_info[0] > 2 +if IS_PY3: + from .proxiedtransport3 import ProxiedTransport +else: + from .proxiedtransport2 import ProxiedTransport class TestLinkHelper(object): """ Helper Class to find out the TestLink connection parameters. @@ -77,8 +83,11 @@ def __init__(self, server_url=None, devkey=None, proxy=None): self._server_url = server_url self._devkey = devkey self._proxy = proxy + self._setParams() + + def _setParams(self): self._setParamsFromEnv() - + def _setParamsFromEnv(self): """ fill empty slots from environment variables _server_url <- TESTLINK_API_PYTHON_SERVER_URL @@ -93,7 +102,7 @@ def _setParamsFromEnv(self): if self._devkey is None: self._devkey = os.getenv(self.ENVNAME_DEVKEY, self.DEFAULT_DEVKEY) - if not self._proxy: + if self._proxy is None: self._proxy = os.getenv(self.ENVNAME_PROXY, self.DEFAULT_PROXY) def _createArgparser(self, usage): @@ -128,16 +137,20 @@ def setParamsFromArgs(self, usage=DEFAULT_DESCRIPTION, args=None): def _getProxiedTransport(self): """ creates and return a ProxiedTransport with self._proxy settings """ - from .proxiedtransport import ProxiedTransport a_pt = ProxiedTransport() a_pt.set_proxy(self._proxy) return a_pt - def connect(self, tl_api_class): + def connect(self, tl_api_class, **kwargs): """ returns a new instance of TL_API_CLASS """ - kwargs = {} if self._proxy: kwargs['transport'] = self._getProxiedTransport() + # must we add an uncertified context for a https connection? + # only, if kwargs not includes a context + if self._server_url.lower().startswith('https'): + if not ('context' in kwargs): + kwargs['context'] = ssl._create_unverified_context() + return tl_api_class(self._server_url, self._devkey, **kwargs) diff --git a/src/testlink/testreporter.py b/src/testlink/testreporter.py new file mode 100644 index 0000000..fed5ee5 --- /dev/null +++ b/src/testlink/testreporter.py @@ -0,0 +1,253 @@ +#! /usr/bin/python +# -*- coding: UTF-8 -*- + +# Copyright 2017-2019 Brian-Willams, TestLink-API-Python-client developers +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------ + + +from testlink.testlinkerrors import TLResponseError, TLArgError + + +class TestReporter(dict): + def __init__(self, tls, testcases, *args, **kwargs): + """This can be given one or more testcases, but they all must have the + same project, plan, and platform. + + TESTCASES must be one or a list of full external testcase id + TLS must be an instance of TestlinkAPIClient, defining a + XMLRPC connection to a TestLink Server""" + super(TestReporter, self).__init__(*args, **kwargs) + self.tls = tls + # handle single testcase + self.testcases = testcases if isinstance(testcases, list) else [testcases] + self._plan_testcases = None + self.remove_non_report_kwargs() + self._platformname_generated = False + + def remove_non_report_kwargs(self): + self.buildname = self.pop('buildname') + + default_note = "created automatically with {}".format(self.__class__.__name__) + self.buildnotes = self.pop('buildnotes', default_note) + self.testplannotes = self.pop('testplannotes', default_note) + self.platformnotes = self.pop('platformnotes', default_note) + + def setup_testlink(self): + """Call properties that may set report kwarg values.""" + self.testprojectname + self.testprojectid + self.testplanid + self.testplanname + self.platformname + self.platformid + self.buildid + + def _get_project_name_by_id(self): + if self.testprojectid: + for project in self.tls.getProjects(): + if project['id'] == self.testprojectid: + return project['name'] + + def _projectname_getter(self): + if not self.get('testprojectname') and self.testprojectid: + self['testprojectname'] = self._get_project_name_by_id() + return self.get('testprojectname') + + @property + def testprojectname(self): + return self._projectname_getter() + + def _get_project_id(self): + tpid = self.get('testprojectid') + if not tpid and self.testprojectname: + self['testprojectid'] = self.tls.getProjectIDByName(self['testprojectname']) + return self['testprojectid'] + return tpid + + def _get_project_id_or_none(self): + project_id = self._get_project_id() + # If not found the id will return as -1 + if project_id == -1: + project_id = None + return project_id + + @property + def testprojectid(self): + self['testprojectid'] = self._get_project_id_or_none() + return self.get('testprojectid') + + @property + def testplanid(self): + return self.get('testplanid') + + @property + def testplanname(self): + return self.get('testplanname') + + @property + def platformname(self): + """Return a platformname added to the testplan if there is one.""" + return self.get('platformname') + + @property + def platformid(self): + return self.get('platformid') + + @property + def buildid(self): + return self.get('buildid') + + @property + def plan_tcids(self): + if not self._plan_testcases: + self._plan_testcases = set() + tc_dict = self.tls.getTestCasesForTestPlan(self.testplanid) + try: + for _, platform in tc_dict.items(): + for k, v in platform.items(): + self._plan_testcases.add(v['full_external_id']) + except AttributeError: + # getTestCasesForTestPlan returns an empty list instead of an empty dict + pass + return self._plan_testcases + + def reportgen(self): + """For use if you need to look at the status returns of individual reporting.""" + self.setup_testlink() + for testcase in self.testcases: + yield self.tls.reportTCResult(testcaseexternalid=testcase, **self) + + def report(self): + for _ in self.reportgen(): + pass + + +class AddTestCaseReporter(TestReporter): + """Add testcase to testplan if not added.""" + def setup_testlink(self): + super(AddTestCaseReporter, self).setup_testlink() + self.ensure_testcases_in_plan() + + def ensure_testcases_in_plan(self): + # Get the platformid if possible or else addition will fail + self.platformid + for testcase in self.testcases: + # Can't check if testcase is in plan_tcids, because that won't work if it's there, but of the wrong platform + try: + self.tls.addTestCaseToTestPlan( + self.testprojectid, self.testplanid, testcase, self.get_latest_tc_version(testcase), + platformid=self.platformid + ) + except TLResponseError as e: + # Test Case version is already linked to Test Plan + if e.code == 3045: + pass + else: + raise + + def get_latest_tc_version(self, testcaseexternalid): + return int(self.tls.getTestCase(None, testcaseexternalid=testcaseexternalid)[0]['version']) + + +class AddTestPlanReporter(TestReporter): + @property + def testplanid(self): + if not self.get('testplanid'): + try: + self['testplanid'] = self.tls.getTestPlanByName(self.testprojectname, self.testplanname)[0]['id'] + except TLResponseError as e: + # Name does not exist + if e.code == 3033: + self['testplanid'] = self._generate_testplanid() + else: + raise + except TypeError: + self['testplanid'] = self._generate_testplanid() + return self['testplanid'] + + def _generate_testplanid(self): + """This won't necessarily be able to create a testplanid. It requires a planname and projectname.""" + if 'testplanname' not in self: + raise TLArgError("Need testplanname to generate a testplan for results.") + + tp = self.tls.createTestPlan(self['testplanname'], self.testprojectname, + notes=self.testplannotes) + self['testplanid'] = tp[0]['id'] + return self['testplanid'] + + +class AddPlatformReporter(TestReporter): + @property + def platformname(self): + """Return a platformname added to the testplan if there is one.""" + pn_kwarg = self.get('platformname') + if pn_kwarg and self._platformname_generated is False: + # If we try to create platform and catch platform already exists error (12000) it sometimes duplicates a + # platformname + try: + self.tls.addPlatformToTestPlan(self.testplanid, pn_kwarg) + except TLResponseError as e: + if int(e.code) == 235: + self.tls.createPlatform(self.testprojectname, pn_kwarg, + notes=self.platformnotes, + platformondesign=True, platformonexecution=True) + self.tls.addPlatformToTestPlan(self.testplanid, pn_kwarg) + self._platformname_generated = True + else: + raise + return pn_kwarg + + @property + def platformid(self): + if not self.get('platformid'): + self['platformid'] = self.getPlatformID(self.platformname) + # This action is idempotent + self.tls.addPlatformToTestPlan(self.testplanid, self.platformname) + return self['platformid'] + + def getPlatformID(self, platformname, _firstrun=True): + """ + This is hardcoded for platformname to always be self.platformname + """ + platforms = self.tls.getTestPlanPlatforms(self.testplanid) + for platform in platforms: + # https://github.com/Brian-Williams/TestLink-API-Python-client/issues/1 + if platform['name'].lower() == platformname.lower(): + return platform['id'] + # Platformname houses platform creation as platform creation w/o a name isn't possible + if not self.platformname: + raise TLArgError( + "Couldn't find platformid for {}.{}, " + "please provide a platformname to generate.".format(self.testplanid, self.platformname) + ) + if _firstrun is True: + return self.getPlatformID(self.platformname, _firstrun=False) + else: + raise TLArgError("PlatformID not found after generated from platformname '{}' " + "in test plan {}.".format(self.platformname, self.testplanid)) + + +class AddBuildReporter(TestReporter): + @property + def buildid(self): + bid = self.get('buildid') + if not bid or bid not in self.tls.getBuildsForTestPlan(self.testplanid): + self['buildid'] = self._generate_buildid() + return self.get('buildid') + + def _generate_buildid(self): + r = self.tls.createBuild(self.testplanid, self.buildname, self.buildnotes) + return r[0]['id'] diff --git a/src/testlink/version.py b/src/testlink/version.py index 884b4e7..4656c6d 100644 --- a/src/testlink/version.py +++ b/src/testlink/version.py @@ -1,7 +1,7 @@ #! /usr/bin/python # -*- coding: UTF-8 -*- -# Copyright 2013-2015 TestLink-API-Python-client developers +# Copyright 2013-2021 TestLink-API-Python-client developers # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -17,6 +17,5 @@ # # ------------------------------------------------------------------------ -VERSION = '0.6.2' -TL_RELEASE = '1.9.14' - +VERSION = '0.8.2-dev141' +TL_RELEASE = '1.9.20-fixed_c88e348ce' diff --git a/test/conftest.py b/test/conftest.py new file mode 100644 index 0000000..0de5044 --- /dev/null +++ b/test/conftest.py @@ -0,0 +1,76 @@ +#! /usr/bin/python +# -*- coding: UTF-8 -*- + +# Copyright 2018-2019 Luiko Czub, TestLink-API-Python-client developers +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------ + +import os.path +import pytest +from testlink import TestlinkAPIClient, TestlinkAPIGeneric, TestLinkHelper + +# example text file attachment = this python file +# why not using os.path.realpath(__file__) +# -> cause __file__ could be compiled python file *.pyc, if the test run is +# repeated without changing the test code +ATTACHMENT_EXAMPLE_TEXT= os.path.join(os.path.dirname(__file__), + os.path.basename(__file__)) + +#attachemantFile = open(ATTACHMENT_EXAMPLE_TEXT, 'r') + +@pytest.fixture() +def attachmentFile(): + ''' open readonly attachment sample before test and close it afterwards ''' + aFile = open(ATTACHMENT_EXAMPLE_TEXT, 'r') + yield aFile + aFile.close() + +@pytest.fixture(scope='session') +def api_helper_class(): + return TestLinkHelper + + +@pytest.fixture(scope='session') +def api_generic_client(api_helper_class): + ''' Init TestlinkAPIGeneric Client with connection parameters defined in + environment variables + TESTLINK_API_PYTHON_DEVKEY and TESTLINK_API_PYTHON_DEVKEY + ''' + return api_helper_class().connect(TestlinkAPIGeneric) + +@pytest.fixture(scope='session') +def api_general_client(api_helper_class): + ''' Init TestlinkAPIClient Client with connection parameters defined in + environment variables + TESTLINK_API_PYTHON_DEVKEY and TESTLINK_API_PYTHON_DEVKEY + ''' + return api_helper_class().connect(TestlinkAPIClient) + +@pytest.fixture(scope='session', params=[TestlinkAPIGeneric, TestlinkAPIClient]) +def api_client_class(request): + ''' all variations of Testlink API Client classes ''' + return request.param + +@pytest.fixture(scope='session') +def api_client(api_client_class, api_helper_class): + ''' Init Testlink API Client class defined in fixtures api_client_class with + connection parameters defined in environment variables + TESTLINK_API_PYTHON_DEVKEY and TESTLINK_API_PYTHON_DEVKEY + + Tests will be call for each Testlink API Client class, defined in + fixtures parameter list + ''' + return api_helper_class().connect(api_client_class) + diff --git a/test/utest-offline/test_apiClients_whatArgs.py b/test/utest-offline/test_apiClients_whatArgs.py new file mode 100644 index 0000000..d6fd686 --- /dev/null +++ b/test/utest-offline/test_apiClients_whatArgs.py @@ -0,0 +1,152 @@ +#! /usr/bin/python +# -*- coding: UTF-8 -*- + +# Copyright 2018-2021 Luiko Czub, TestLink-API-Python-client developers +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------ + +# TestCases for Testlink API clients whatArgs calls +# - TestlinkAPIClient, TestlinkAPIGeneric +# + +import pytest +import re + +def test_whatArgs_noArgs(api_client): + response = api_client.whatArgs('sayHello') + assert re.match('sayHello().*', response) + +def test_whatArgs_onlyOptionalArgs(api_client): + response = api_client.whatArgs('getTestCaseKeywords') + assert re.match(r'getTestCaseKeywords\(\[.*=<.*>\].*\).*', + response) + +def test_whatArgs_OptionalAndPositionalArgs(api_client): + response = api_client.whatArgs('createBuild') + assert re.match(r'createBuild\(<.*>.*\).*', response) + +def test_whatArgs_MandatoryArgs(api_client): + response = api_client.whatArgs('uploadExecutionAttachment') + assert re.match(r'uploadExecutionAttachment\(, <.*>.*\).*', + response) + +def test_whatArgs_unknownMethods(api_client): + response = api_client.whatArgs('apiUnknown') + assert re.match(r"callServerWithPosArgs\('apiUnknown', \[apiArg=\]\)", + response) + +test_data_apiCall_descriptions_equal_all = [ + ('getTestCasesForTestSuite', ['getkeywords=']), + ('reportTCResult', ['user=', 'execduration=', + 'timestamp=', 'steps=', + "[{'step_number' : 6,"]), + ('getLastExecutionResult', ['options=','getBugs']), + ('getTestCasesForTestPlan', [',', + 'buildid=', 'platformid=', + 'testcaseid=', 'keywordid=', + 'keywords=', 'executed=', + 'assignedto=', 'executestatus=', + 'executiontype=', 'getstepinfo=', + 'details=
', 'customfields=', + 'keywordid - keywords']), + ('createTestCase', [',', ',', ',', + ',', ',', #',', + 'preconditions=', + 'importance=', + 'executiontype=', 'order=', + 'internalid=', + 'checkduplicatedname=', + 'actiononduplicatedname=', + 'status=', + 'estimatedexecduration=']), + ('createTestPlan', ['prefix=', 'testprojectname=']), + ('getTestSuite',['', '']), + ('updateTestSuite',[',', 'testprojectid=', + 'prefix=', 'parentid=', + 'testsuitename=', 'details=
', + 'order=']), + ('createBuild',[',', ',', 'active=', + 'copytestersfrombuild=']), + ('addTestCaseToTestPlan',[',', ',', + ',', ',', + 'platformid=', + 'executionorder=', + 'urgency=', 'overwrite=']), + ('createTestProject',[',', ',', + 'notes=', 'active=', + 'public=', 'options=', + 'itsname=', 'itsenabled=']), + ('getIssueTrackerSystem',[',']), + ('getExecutionSet',[',', 'testcaseid=', + 'testcaseexternalid=', + 'buildid=', 'buildname=', + 'platformid=', + 'platformname=', 'options=']), + ('getRequirements',[',', 'testplanid=', + 'platformid=']), + ('getReqCoverage',[',', ',']), + ('setTestCaseTestSuite',[',', ',']), + ('getTestSuiteAttachments',[',']), + ('getAllExecutionsResults',[',','testcaseid=', + 'testcaseexternalid=', + 'platformid=', 'buildid=', + 'options=']), + ('getTestCaseAttachments',['version=', + 'testcaseexternalid=']), + ('uploadTestCaseAttachment',[',', ',', + 'title=', 'description=<description>', + 'filename=<filename>', 'filetype=<filetype>', + 'content=<content>']), + ('createPlatform',['<testprojectname>,', '<platformname>,', 'notes=<notes>', + 'platformondesign=<platformondesign>', + 'platformonexecution=<platformonexecution>']), + ('closeBuild', ['<buildid>']), + ('createUser', ['<login>', '<firstname>', '<lastname>', '<email>', + 'password=<password>']), + ('setUserRoleOnProject', ['<userid>', '<rolename>', '<testprojectid>']) + ] + +@pytest.mark.parametrize("apiCall, descriptions", + test_data_apiCall_descriptions_equal_all) +def test_whatArgs_apiCall_descriptions_equal_all(api_client, apiCall, descriptions): + argsDescription = api_client.whatArgs(apiCall) + for parts in descriptions: + assert parts in argsDescription + +test_data_apiCall_descriptions_only_generic = [ + ('createTestCase', ['<steps>,']), + ('createBuild',['buildnotes=<buildnotes>']), + ('getTestCaseAttachments',['testcaseid=<testcaseid>']) + ] +@pytest.mark.parametrize("apiCall, descriptions", + test_data_apiCall_descriptions_only_generic) +def test_whatArgs_apiCall_descriptions_only_generic(api_generic_client, apiCall, descriptions): + argsDescription = api_generic_client.whatArgs(apiCall) + for parts in descriptions: + assert parts in argsDescription + +test_data_apiCall_descriptions_only_general = [ + ('createTestCase', ['steps=<steps>']), + ('createBuild',['<buildnotes>,']), + ('getTestCaseAttachments',['<testcaseid>,']) + ] +@pytest.mark.parametrize("apiCall, descriptions", + test_data_apiCall_descriptions_only_general) +def test_whatArgs_apiCall_descriptions_only_general(api_general_client, apiCall, descriptions): + argsDescription = api_general_client.whatArgs(apiCall) + for parts in descriptions: + assert parts in argsDescription + + \ No newline at end of file diff --git a/test/utest-offline/test_py3_vs_py2.py b/test/utest-offline/test_py3_vs_py2.py new file mode 100644 index 0000000..aa6c84e --- /dev/null +++ b/test/utest-offline/test_py3_vs_py2.py @@ -0,0 +1,31 @@ +#! /usr/bin/python +# -*- coding: UTF-8 -*- + +# Copyright 2018-2020 Luiko Czub, TestLink-API-Python-client developers +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------ + +# TestCases for Testlink API clients handling py2 and py3 differences +# - TestlinkAPIClient, TestlinkAPIGeneric +# + +import pytest +#from conftest import api_general_client, api_generic_client + +def test_IS_PY3_same_state(): + from testlink.testlinkhelper import IS_PY3 as proxie_is_py3 + from testlink.testlinkapigeneric import IS_PY3 as tl_is_py3 + assert proxie_is_py3 == tl_is_py3 + diff --git a/test/utest-offline/testlinkapi_offline_test.py b/test/utest-offline/testlinkapi_offline_test.py index c8d7c91..0e09db3 100644 --- a/test/utest-offline/testlinkapi_offline_test.py +++ b/test/utest-offline/testlinkapi_offline_test.py @@ -1,7 +1,7 @@ #! /usr/bin/python # -*- coding: UTF-8 -*- -# Copyright 2012-2015 Luiko Czub, TestLink-API-Python-client developers +# Copyright 2012-2019 Luiko Czub, TestLink-API-Python-client developers # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -21,14 +21,7 @@ # no calls are send to a TestLink Server import sys - -IS_PY26 = False -if sys.version_info[0] == 2 and sys.version_info[1] == 6: - # py26 needs backport unittest2 - import unittest2 as unittest - IS_PY26 = True -else: - import unittest +import unittest if sys.version_info[0] == 2 and sys.version_info[1] == 7: # py27 and py31 assertRaisesRegexp was renamed in py32 to assertRaisesRegex @@ -287,6 +280,7 @@ def __init__(self, server_url, devKey, **args): def loadScenario(self, a_scenario): self.scenario_data = a_scenario + self.callArgs = None def _callServer(self, methodAPI, argsAPI=None): self.callArgs = argsAPI @@ -334,8 +328,12 @@ class TestLinkAPIOfflineTestCase(unittest.TestCase): {'step_number' : '3', 'actions' : "action C" , 'expected_results' : "result C", 'execution_type' : "0"}] + @classmethod + def setUpClass(cls): + cls.api = TestLinkHelper().connect(DummyAPIClient) + def setUp(self): - self.api = TestLinkHelper().connect(DummyAPIClient) + self.api._emptyStepsList() # def tearDown(self): # pass @@ -564,12 +562,6 @@ def test_reportTCResult_user(self): self.assertEqual(self.api.devKey, self.api.callArgs['devKey']) self.assertEqual('a login name', self.api.callArgs['user']) - def test_whatArgs_reportTCResult(self): - argsDescription = self.api.whatArgs('reportTCResult') - self.assertIn('user=<user>', argsDescription) - self.assertIn('execduration=<execduration>', argsDescription) - self.assertIn('timestamp=<timestamp>', argsDescription) - def test_getTestCasesForTestSuite_keyWords(self): self.api.loadScenario(SCENARIO_KEYWORDS) response = self.api.getTestCasesForTestSuite('deepFalse3', False, @@ -578,10 +570,6 @@ def test_getTestCasesForTestSuite_keyWords(self): self.assertNotIn('keywords', response[2]) self.assertEqual(self.api.devKey, self.api.callArgs['devKey']) - def test_whatArgs_getTestCasesForTestSuite(self): - argsDescription = self.api.whatArgs('getTestCasesForTestSuite') - self.assertIn('getkeywords=<getkeywords>', argsDescription) - def test_listKeywordsForTC_FullExternalId(self): self.api.loadScenario(SCENARIO_KEYWORDS) response = self.api.listKeywordsForTC('NPROAPI-2') @@ -642,30 +630,25 @@ def test_listKeywordsForTS_Multi(self): self.assertEqual({'8144': set(['KeyWord01', 'KeyWord03']), '8159': set(['KeyWord02']), '8169': set()}, set_response) - def test_whatArgs_getLastExecutionResult(self): - argsDescription = self.api.whatArgs('getLastExecutionResult') - self.assertIn('options=<options>', argsDescription) - self.assertIn('getBugs', argsDescription) - - def test_whatArgs_createTestCase(self): - argsDescription = self.api.whatArgs('createTestCase') - self.assertIn('executiontype=<executiontype>', argsDescription) - self.assertIn('executiontype, order', argsDescription) - def test_connect_with_proxy(self): """ create a TestLink API dummy with ProxiedTransport""" self.api = DummyAPIClient('http://SERVER-URL-71', 'DEVKEY-71', transport='PROXY-71') - if not IS_PY26: - # Py 26 does not define a __call__ method and getattr is overriden - # to created a request and return the response - # -> so no access to attribute __transport with Py26 - self.assertEqual('PROXY-71', self.api.server.__call__('transport')) - - def test_whatArgs_createTestPlan(self): - argsDescription = self.api.whatArgs('createTestPlan') - self.assertIn('prefix=<prefix>', argsDescription) - self.assertIn('testprojectname=<testprojectname>', argsDescription) + self.assertEqual('PROXY-71', self.api.server.__call__('transport')) + + def test_connect_with_use_datetime(self): + """ create a TestLink Generic API dummy with use_datetime""" + self.api = DummyAPIClient('http://SERVER-URL-71', 'DEVKEY-71', + use_datetime='datetime?') + a_transport = self.api.server.__call__('transport') + self.assertEqual('datetime?', a_transport._use_datetime) + + def test_connect_with_context(self): + """ create a TestLink Generic API dummy with use_datetime""" + self.api = DummyAPIClient('https://SERVER-URL-71', 'DEVKEY-71', + context='ssl_context') + a_transport = self.api.server.__call__('transport') + self.assertEqual('ssl_context', a_transport.context) if __name__ == "__main__": #import sys;sys.argv = ['', 'Test.testName'] diff --git a/test/utest-offline/testlinkapigeneric_offline_test.py b/test/utest-offline/testlinkapigeneric_offline_test.py index dd2367a..808357e 100644 --- a/test/utest-offline/testlinkapigeneric_offline_test.py +++ b/test/utest-offline/testlinkapigeneric_offline_test.py @@ -1,781 +1,786 @@ -#! /usr/bin/python -# -*- coding: UTF-8 -*- - -# Copyright 2013-2015 Luiko Czub, TestLink-API-Python-client developers -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# ------------------------------------------------------------------------ - -# this test works WITHOUT an online TestLink Server -# no calls are send to a TestLink Server - -import sys, os.path - -IS_PY26 = False -if sys.version_info[0] == 2 and sys.version_info[1] == 6: - # py26 needs backport unittest2 - import unittest2 as unittest - IS_PY26 = True -else: - import unittest - -if sys.version_info[0] == 2 and sys.version_info[1] == 7: - # py27 and py31 assertRaisesRegexp was renamed in py32 to assertRaisesRegex - unittest.TestCase.assertRaisesRegex = unittest.TestCase.assertRaisesRegexp - # py27 and py31 assertRegexpMatches was renamed in py32 to assertRegex - unittest.TestCase.assertRegex = unittest.TestCase.assertRegexpMatches - - -from testlink import TestlinkAPIGeneric, TestLinkHelper -from testlink.testlinkerrors import TLArgError, TLResponseError, TLAPIError - - -#from testlink.testlinkapigeneric import positionalArgNamesDefault -# scenario_a includes response from a testlink 1.9.8 server -SCENARIO_A = {'repeat' : 'You said: One World', - 'sayHello' : 'Hey Folks!', - 'doesUserExist' : { - 'Big Bird' : [{'message': '(doesUserExist) - Cannot Find User Login provided (Big Bird).', - 'code': 10000}], - 'admin' : True }, - 'getProjectTestPlans' : { - 'onePlan' : [{'name': 'TestPlan_API', - 'notes': 'New TestPlan created with the API', - 'active': '1', 'is_public': '1', - 'testproject_id': '21', 'id': '22'}] , - 'noPlan' : '' }, - 'getBuildsForTestPlan' : {'noBuild' : '' }, - 'getTestPlanPlatforms' : { - 'twoPlatforms' : [{'notes': '', 'id': '1', 'name': 'dutch'}, - {'notes': '', 'id': '2', 'name': 'platt'}], - 'noPlatform' : [{'message': 'Test plan (noPlatform) has no platforms linked', - 'code': 3041}]}, - 'getTestSuitesForTestPlan' : {'noSuite' : ''}, - 'getTestSuitesForTestSuite' : {'noSuite' : ''}, - 'getFirstLevelTestSuitesForTestProject' : { - 'noSuite' : [{'message': 'Test Project (noSuite) is empty.', - 'code': 7008}]}, - 'getTestCasesForTestSuite' : {'noTestCase' : [] }, - 'getTestCasesForTestPlan' : {'noTestCase' : [] }, - 'getTestCaseIDByName' : { - 'dictResult' : {'1': {'parent_id': '24', 'tc_external_id': '2', - 'id': '33', 'tsuite_name': 'B - First Level', - 'name': 'TESTCASE_B'}}, - 'listResult' : [{'parent_id': '25', 'tc_external_id': '1', - 'id': '26', 'tsuite_name': 'AA - Second Level', - 'name': 'TESTCASE_AA'}]}, - 'getProjectPlatforms' : { - 'twoPlatforms' : {'dutch' : {'id': '1', 'name': 'dutch'}, - 'platt' : {'id': '2', 'name': 'platt'}}, - 'noPlatform' : {} - }, - 'reportTCResult' : [{'status': True, 'operation': 'reportTCResult', - 'message': 'Success!', 'overwrite': False, 'id': '773'}], - 'getProjectKeywords' : { - 'twoKeywords' : {'25': 'KeyWord01', '26': 'KeyWord02'}, - 'noKeyword' : {} - }, - 'getTestCaseKeywords' : { - 'twoKeywords' : {'25': 'KeyWord01', '26': 'KeyWord02'}, - 'noKeyword' : {} - } - } - -# scenario_tl198 used by test with older responses, changed in TL 1.9.9 -SCENARIO_TL198 = {'testLinkVersion' : 'unknown', - 'about' : 'Testlink API Version: 1.0 ...'} - -# scenario_tl199 used by test with newer responses, changed in TL 1.9.9 -SCENARIO_TL199 = {'testLinkVersion' : '1.9.9', - 'about' : 'Testlink API Version: 1.0 ...'} - -# scenario_custom_fields defines response for custom field request -# {'default_value': '', 'enable_on_execution': '1', 'name': 'cf_tc_ex_string', 'location': '1', 'enable_on_design': '0', 'valid_regexp': '', 'length_min': '0', 'possible_values': '', 'value': 'a custom string', 'label': 'CF Exec String', 'show_on_testplan_design': '0', 'display_order': '1', 'length_max': '0', 'show_on_design': '0', 'required': '0', 'show_on_execution': '1', 'type': '0', 'id': '24', 'node_id': '7691', 'enable_on_testplan_design': '0'} -SCENARIO_CUSTOM_FIELDS = { - 'getTestCaseCustomFieldDesignValue' : { - 'cf_notAssigned' : [{'message': '(getTestCaseCustomFieldDesignValue) - Custom Field (name:cf_tc_sd_string), is not assigned to Test Project(name=PROJECT_API_GENERIC-8 / id=7760)', - 'code': 9003}], - 'cf_full' : {'default_value': '', 'enable_on_execution': '0', 'name': 'cf_tc_sd_string', - 'location': '1', 'enable_on_design': '1', 'valid_regexp': '', 'length_min': '0', 'possible_values': '', - 'value': 'a custom spec design string', 'label': 'CF SpecDesign String', 'show_on_testplan_design': '0', - 'display_order': '1', 'length_max': '0', 'show_on_design': '1', 'required': '0', 'show_on_execution': '1', - 'type': '0', 'id': '22', 'node_id': '7691', 'enable_on_testplan_design': '0'}, - 'cf_value' : 'a custom spec design string', - 'cf_valueEmpty' : '', - 'cf_simple' : {'type': '0', 'name': 'cf_tc_sd_string', - 'value': 'a custom spec design string', 'label': 'CF SpecDesign String'} - }, - 'updateTestCaseCustomFieldDesignValue' : { - 'cf_notAssigned' : '', - 'a_string' : '' - }, - - 'getTestCaseCustomFieldExecutionValue' : { - 'cf_notAssigned' : '', - 'cf_full' : {'default_value': '', 'enable_on_execution': '1', 'name': 'cf_tc_ex_string', - 'location': '1', 'enable_on_design': '0', 'valid_regexp': '', 'length_min': '0', 'possible_values': '', - 'value': 'a custom exec string', 'label': 'CF Exec String', 'show_on_testplan_design': '0', - 'display_order': '1', 'length_max': '0', 'show_on_design': '0', 'required': '0', 'show_on_execution': '1', - 'type': '0', 'id': '24', 'node_id': '7691', 'enable_on_testplan_design': '0'} - }, - - 'getTestCaseCustomFieldTestPlanDesignValue' : { - 'cf_notAssigned' : '', - 'cf_full' : {'default_value': '', 'enable_on_execution': '0', 'name': 'cf_tc_pd_string', - 'enable_on_design': '0', 'valid_regexp': '', 'length_min': '0', 'possible_values': '', - 'value': 'a custom PlanDesign string', 'label': 'CF PlanDesign String', 'show_on_testplan_design': '1', - 'display_order': '1', 'length_max': '0', 'show_on_design': '0', 'required': '0', 'show_on_execution': '1', - 'type': '0', 'id': '28', 'node_id': '779', 'enable_on_testplan_design': '1'} - }, - 'getTestSuiteCustomFieldDesignValue' : { - 'cf_notAssigned' : '', - 'cf_full' : {'default_value': '', 'enable_on_execution': '0', 'name': 'cf_ts_string', 'location': '1', - 'enable_on_design': '1', 'valid_regexp': '', 'length_min': '0', 'possible_values': '', - 'value': 'a custom TSuite string', 'label': 'CF TestSuite String', 'show_on_testplan_design': '0', - 'display_order': '1', 'length_max': '0', 'show_on_design': '1', 'required': '0', 'show_on_execution': '1', - 'type': '0', 'id': '30', 'node_id': '', 'enable_on_testplan_design': '0'} - }, - - 'getTestPlanCustomFieldDesignValue' : { - 'cf_notAssigned' : '', - 'cf_full' : {'default_value': '', 'enable_on_execution': '0', 'name': 'cf_tp_string', 'location': '1', - 'enable_on_design': '1', 'valid_regexp': '', 'length_min': '0', 'possible_values': '', - 'value': 'a custom TPlan string', 'label': 'CF TPlan String', 'show_on_testplan_design': '0', - 'display_order': '1', 'length_max': '0', 'show_on_design': '1', 'required': '0', 'show_on_execution': '1', - 'type': '0', 'id': '31', 'node_id': '', 'enable_on_testplan_design': '0'} - }, - - 'getReqSpecCustomFieldDesignValue' : { - 'cf_notAssigned' : '', - 'cf_full' : {'default_value': '', 'enable_on_execution': '0', 'name': 'cf_req_sd_string', 'location': '1', - 'enable_on_design': '1', 'valid_regexp': '', 'length_min': '0', 'possible_values': '', - 'value': 'a custom ReqSpec string', 'label': 'CF ReqSpec String', 'show_on_testplan_design': '0', - 'display_order': '1', 'length_max': '0', 'show_on_design': '1', 'required': '0', 'show_on_execution': '0', - 'type': '0', 'id': '32', 'node_id': '', 'enable_on_testplan_design': '0'} - }, - 'getRequirementCustomFieldDesignValue' : { - 'cf_notAssigned' : '', - 'cf_full' : {'default_value': '', 'enable_on_execution': '0', 'name': 'cf_req_string', 'location': '1', - 'enable_on_design': '1', 'valid_regexp': '', 'length_min': '0', 'possible_values': '', - 'value': 'a custom Req string', 'label': 'CF Req String', 'show_on_testplan_design': '0', - 'display_order': '1', 'length_max': '0', 'show_on_design': '1', 'required': '0', 'show_on_execution': '0', - 'type': '0', 'id': '33', 'node_id': '', 'enable_on_testplan_design': '0'} - } - - } - -# scenario_keywords defines response with keywords -SCENARIO_KEYWORDS = {'getTestCasesForTestSuite' : { - 'noTestCase' : [] , - 'keyWords' : [{'node_order': '0', 'is_open': '1', - 'keywords': {'1': {'keyword_id': '1', 'notes': 'a key word', 'testcase_id': '8144', 'keyword': 'KeyWord01'}, - '3': {'keyword_id': '3', 'notes': 'a third key word', 'testcase_id': '8144', 'keyword': 'KeyWord03'}}, - 'id': '8144', 'node_type_id': '3', 'layout': '1', 'tc_external_id': '2', 'parent_id': '8134', - 'version': '1', 'estimated_exec_duration': '3.00', 'updater_id': '2', 'status': '1', - 'tsuite_name': 'B - First Level', 'importance': '3', 'modification_ts': '2014-06-30 20:45:40', - 'execution_type': '1', 'preconditions': '<p>\n\tthese are the preconditions</p>\n', 'active': '1', - 'creation_ts': '2014-06-28 22:06:17', 'node_table': 'testcases', 'tcversion_id': '8145', - 'name': 'TESTCASE_B', 'summary': '<p>\n\tThis is the summary of the Test Case B</p>\n', - 'steps': [{'step_number': '1', 'actions': 'Step action 1 -b ', 'execution_type': '2', 'active': '1', 'id': '8151', 'expected_results': 'Step result 1 - b'}, - {'step_number': '2', 'actions': 'Step action 2 -b ', 'execution_type': '2', 'active': '1', 'id': '8152', 'expected_results': 'Step result 2 - b'}, - {'step_number': '3', 'actions': 'action 3 createTestCaseSteps.update', 'execution_type': '2', 'active': '1', 'id': '8153', 'expected_results': 'update - cause step 3 already exist'}, - {'step_number': '4', 'actions': 'Step action 4 -b ', 'execution_type': '2', 'active': '1', 'id': '8154', 'expected_results': 'Step result 4 - b'}, - {'step_number': '5', 'actions': 'Step action 5 -b changed by updateTestCase', 'execution_type': '2', 'active': '1', 'id': '8155', 'expected_results': 'Step result 5 - b changed'}, - {'step_number': '6', 'actions': 'Step action 6 -b added by updateTestCase', 'execution_type': '2', 'active': '1', 'id': '8156', 'expected_results': 'Step result 6 - b added'}, - {'step_number': '7', 'actions': 'action 7 createTestCaseSteps.create', 'execution_type': '2', 'active': '1', 'id': '8157', 'expected_results': 'create - cause step 7 not yet exist'}, - {'step_number': '8', 'actions': 'action 8 createTestCaseSteps.update', 'execution_type': '2', 'active': '1', 'id': '8158', 'expected_results': 'create - cause step 8 not yet exist'}], - 'author_id': '1', 'external_id': 'GPROAPI10-2'}] - } - } - -# scenario_no_project simulates a fresh empty test link application -SCENARIO_NO_PROJECT = {'getProjects' : [] } - -# example text file attachment = this python file -# why not using os.path.realpath(__file__) -# -> cause __file__ could be compiled python file *.pyc, if the test run is -# repeated without changing the test code -ATTACHMENT_EXAMPLE_TEXT= os.path.join(os.path.dirname(__file__), - 'testlinkapigeneric_offline_test.py') - - -class DummyAPIGeneric(TestlinkAPIGeneric): - """ Dummy for Simulation TestLinkAPIGeneric. - Overrides - - _callServer() Method to return test scenarios - - extend positional_arg_names for method 'DummyMethod' - """ - - __slots__ = ['scenario_data', 'callArgs'] - - def __init__(self, server_url, devKey, **args): - super(DummyAPIGeneric, self).__init__(server_url, devKey, **args) - self._positionalArgNames['DummyMethod'] = ['Uno', 'due', 'tre'] - self.scenario_data = {} - self.callArgs = None - - - def loadScenario(self, a_scenario): - self.scenario_data = a_scenario - - def _callServer(self, methodAPI, argsAPI=None): - self.callArgs = argsAPI - response = None - if methodAPI in ['DummyMethod']: - response = [argsAPI] - else: - data = self.scenario_data[methodAPI] - if methodAPI in ['doesUserExist']: - response = data[argsAPI['user']] - elif methodAPI in ['getProjectTestPlans', 'getProjectPlatforms', - 'getFirstLevelTestSuitesForTestProject', - 'getProjectKeywords']: - response = data[argsAPI['testprojectid']] - elif methodAPI in ['getBuildsForTestPlan', 'getTestPlanPlatforms', - 'getTestSuitesForTestPlan', 'getTestCasesForTestPlan']: - response = data[argsAPI['testplanid']] - elif methodAPI in ['getTestSuitesForTestSuite', - 'getTestCasesForTestSuite']: - response = data[argsAPI['testsuiteid']] - elif methodAPI in ['getTestCaseIDByName']: - response = data[argsAPI['testcasename']] - elif methodAPI in ['getTestCaseKeywords']: - response = data[argsAPI['testcaseid']] - elif methodAPI in ['testLinkVersion']: - response = data - if data == 'unknown': - raise TLAPIError('problems calling the API method testLinkVersion1') - elif methodAPI == 'updateTestCaseCustomFieldDesignValue': - response = data[argsAPI['customfields']['cf_field1']] - elif 'CustomField' in methodAPI: - response = data[argsAPI['customfieldname']] - else: - response = data - return response - - -class TestLinkAPIGenericOfflineTestCase(unittest.TestCase): - """ TestCases for TestlinkAPIGeneric - does not interacts with a TestLink Server. - works with DummyAPIGeneric which returns special test data - """ - - def setUp(self): - self.api = TestLinkHelper().connect(DummyAPIGeneric) - self.callArgs = None - -# def tearDown(self): -# pass - - - def test_convertPositionalArgs(self): - response = self.api._convertPostionalArgs('DummyMethod', [1,2,3]) - self.assertEqual({'Uno' : 1, 'due' :2, 'tre' : 3}, response) - - def test__convertPositionalArgs_missingConf(self): - client = self.api - def a_func(a_api): a_api._convertPostionalArgs('NoConfigMethod', [1,2]) - self.assertRaises(TLArgError, a_func, client) - - def test__convertPositionalArgs_lessValues(self): - client = self.api - def a_func(a_api): a_api._convertPostionalArgs('DummyMethod', [1,2]) - self.assertRaises(TLArgError, a_func, client) - - def test__convertPositionalArgs_moreValues(self): - client = self.api - def a_func(a_api): a_api._convertPostionalArgs('DummyMethod', [1,2,3,4]) - self.assertRaises(TLArgError, a_func, client) - - def test_callServerWithPosArgs_pos(self): - self.api.callServerWithPosArgs('DummyMethod', 1,2,3) - self.assertEqual({'Uno' : 1, 'due' :2, 'tre' : 3}, self.api.callArgs) - - def test_callServerWithPosArgs_pos_opt(self): - self.api.callServerWithPosArgs('DummyMethod', 1,2,3, quad=4) - self.assertEqual({'Uno' : 1, 'due' :2, 'tre' : 3, 'quad' : 4}, self.api.callArgs) - - def test_callServerWithPosArgs_opt(self): - self.api.callServerWithPosArgs('DummyMethod', quad=4) - self.assertEqual({'quad' : 4}, self.api.callArgs) - - def test_callServerWithPosArgs_none(self): - self.api.callServerWithPosArgs('DummyMethod') - self.assertEqual({}, self.api.callArgs) - - def test_checkResponse_emptyResponse(self): - client = self.api - def a_func(a_api, response): - a_api._checkResponse(response, 'DummyMethod', - {'Uno' : 1, 'due' :2, 'tre' : 3}) - self.assertRaises(TLResponseError, a_func, client, '') - self.assertRaises(TLResponseError, a_func, client, []) - - def test_checkResponse_errorResponse(self): - client = self.api - responseA = [{'message': '(reportTCResult) - TC ID 709 does not exist!', - 'code': 5000}] - def a_func(a_api, response): - a_api._checkResponse(response, 'DummyMethod', - {'Uno' : 1, 'due' :2, 'tre' : 3}) - self.assertRaises(TLResponseError, a_func, client, responseA) - - def test_checkResponse_okResponse(self): - self.api._checkResponse( - [{'message': 'all fine, cause no key with name code'}], - 'DummyMethod', {'Uno' : 1, 'due' :2, 'tre' : 3}) - self.api._checkResponse( - 'some API Call juts returns one string without codes', - 'DummyMethod', {'Uno' : 1, 'due' :2, 'tre' : 3}) - - def test_checkResponse_booleanResponse(self): - response = True - self.api._checkResponse(response, 'DummyMethod', - {'Uno' : 1, 'due' :2, 'tre' : 3}) - - def test_checkResponse_dictionaryResponse(self): - response = {'note' : 'uploadAttachment Calls return {..} and not [{..}]'} - self.api._checkResponse(response, 'DummyMethod', - {'Uno' : 1, 'due' :2, 'tre' : 3}) - - def test_checkResponse_errorResponse_sringCode(self): - client = self.api - - responseA = [{'message': '(getUserByID) - Cannot Find User with DB ID (4711).', - 'code': 'NO_USER_BY_ID_LOGIN'}] - def a_func(a_api, response): - a_api._checkResponse(response, 'getUserByID', - {'userid' : 4711}) - self.assertRaises(TLResponseError, a_func, client, responseA) - - def test__apiMethodArgNames_noArgs(self): - response = self.api._apiMethodArgNames('sayHello') - self.assertEqual(response, ([], [], [])) - - def test_whatArgs_noArgs(self): - response = self.api.whatArgs('sayHello') - self.assertRegex(response, 'sayHello().*') - - def test__apiMethodArgNames_onlyOptionalArgs(self): - response = self.api._apiMethodArgNames('getTestCaseAttachments') - self.assertEqual(response[0], []) - self.assertGreater(len(response[1]), 0) - self.assertEqual(response[2], []) - - def test_whatArgs_onlyOptionalArgs(self): - response = self.api.whatArgs('getTestCaseAttachments') - self.assertRegex(response, 'getTestCaseAttachments\(\[.*=<.*>\].*\).*') - - def test__apiMethodArgNames__OptionalAndPositionalArgs(self): - response = self.api._apiMethodArgNames('createBuild') - self.assertGreater(len(response[0]), 0) - self.assertGreater(len(response[1]), 0) - self.assertEqual(response[2], []) - - def test_whatArgs_OptionalAndPositionalArgs(self): - response = self.api.whatArgs('createBuild') - self.assertRegex(response, 'createBuild\(<.*>.*\).*') - - def test__apiMethodArgNames__MandatoryArgs(self): - response = self.api._apiMethodArgNames('uploadExecutionAttachment') - self.assertGreater(len(response[0]), 0) - self.assertGreater(len(response[1]), 0) - self.assertGreater(len(response[2]), 0) - - def test_whatArgs_MandatoryArgs(self): - response = self.api.whatArgs('uploadExecutionAttachment') - self.assertRegex(response, - 'uploadExecutionAttachment\(<attachmentfile>, <.*>.*\).*') - - def test_whatArgs_unknownMethods(self): - response = self.api.whatArgs('apiUnknown') - self.assertRegex(response, - "callServerWithPosArgs\('apiUnknown', \[apiArg=<apiArg>\]\)") - - def test_noWrapperName_apiMethods(self): - " decorator test: API Methods internal function name should be unchanged " - - # apiMethod with decorator @decoApiCallWithoutArgs - self.assertEqual('sayHello', self.api.sayHello.__name__) - # apiMethod with decorator @decoApiCallWithArgs - self.assertEqual('repeat', self.api.repeat.__name__) - # apiMethod with decorator @decoApiCallAddDevKey - self.assertEqual('createBuild', self.api.createBuild.__name__) - # apiMethod with decorator @decoMakerApiCallReplaceTLResponseError() - self.assertEqual('getProjectTestPlans', self.api.getProjectTestPlans.__name__) - # apiMethod with decorator @decoApiCallAddAttachment - self.assertEqual('uploadExecutionAttachment', self.api.uploadExecutionAttachment.__name__) - - def test_ping(self): - self.api.loadScenario(SCENARIO_A) - response = self.api.ping() - self.assertEqual('Hey Folks!', response) - - - def test_getProjectTestPlans_noPlan(self): - self.api.loadScenario(SCENARIO_A) - response = self.api.getProjectTestPlans('noPlan') - self.assertEqual([], response) - self.assertEqual(self.api.devKey, self.api.callArgs['devKey']) - - def test_getProjectTestPlans_onePlan(self): - self.api.loadScenario(SCENARIO_A) - response = self.api.getProjectTestPlans('onePlan') - self.assertEqual('21', response[0]['testproject_id']) - self.assertEqual(1, len(response)) - - def test_getProjectPlatforms_noPlatform(self): - self.api.loadScenario(SCENARIO_A) - response = self.api.getProjectPlatforms('noPlatform') - self.assertEqual({}, response) - self.assertEqual(self.api.devKey, self.api.callArgs['devKey']) - - def test_getProjectPlatforms_twoPlatforms(self): - self.api.loadScenario(SCENARIO_A) - response = self.api.getProjectPlatforms('twoPlatforms') - self.assertEqual('1', response['dutch']['id']) - self.assertEqual(2, len(response)) - - - def test_getBuildsForTestPlan_noBuild(self): - self.api.loadScenario(SCENARIO_A) - response = self.api.getBuildsForTestPlan('noBuild') - self.assertEqual([], response) - self.assertEqual(self.api.devKey, self.api.callArgs['devKey']) - - def test_getTestPlanPlatforms_noPlatform(self): - self.api.loadScenario(SCENARIO_A) - response = self.api.getTestPlanPlatforms('noPlatform') - self.assertEqual([], response) - self.assertEqual(self.api.devKey, self.api.callArgs['devKey']) - - def test_getTestPlanPlatforms_twoPlatforms(self): - self.api.loadScenario(SCENARIO_A) - response = self.api.getTestPlanPlatforms('twoPlatforms') - self.assertEqual('dutch', response[0]['name']) - self.assertEqual(2, len(response)) - - def test_getTestSuitesForTestPlan_noSuite(self): - self.api.loadScenario(SCENARIO_A) - response = self.api.getTestSuitesForTestPlan('noSuite') - self.assertEqual([], response) - self.assertEqual(self.api.devKey, self.api.callArgs['devKey']) - - def test_getTestSuitesForTestSuite_noSuite(self): - self.api.loadScenario(SCENARIO_A) - response = self.api.getTestSuitesForTestSuite('noSuite') - self.assertEqual([], response) - self.assertEqual(self.api.devKey, self.api.callArgs['devKey']) - - def test_getFirstLevelTestSuitesForTestProject_noSuite(self): - self.api.loadScenario(SCENARIO_A) - response = self.api.getFirstLevelTestSuitesForTestProject('noSuite') - self.assertEqual([], response) - self.assertEqual(self.api.devKey, self.api.callArgs['devKey']) - - def test_getTestCasesForTestSuite_noTestCase(self): - self.api.loadScenario(SCENARIO_A) - response = self.api.getTestCasesForTestSuite('noTestCase') - self.assertEqual([], response) - self.assertEqual(self.api.devKey, self.api.callArgs['devKey']) - - def test_getTestCasesForTestSuite_keyWords(self): - self.api.loadScenario(SCENARIO_KEYWORDS) - response = self.api.getTestCasesForTestSuite('keyWords', details='full', - getkeywords=True) - self.assertIn('keywords', response[0]) - self.assertEqual(self.api.devKey, self.api.callArgs['devKey']) - - def test_whatArgs_getTestCasesForTestSuite(self): - argsDescription = self.api.whatArgs('getTestCasesForTestSuite') - self.assertIn('getkeywords=<getkeywords>', argsDescription) - - def test_getTestCasesForTestPlan_noTestCase(self): - self.api.loadScenario(SCENARIO_A) - response = self.api.getTestCasesForTestPlan('noTestCase') - self.assertEqual([], response) - self.assertEqual(self.api.devKey, self.api.callArgs['devKey']) - - def test_getTestCaseIDByName_dictResult(self): - self.api.loadScenario(SCENARIO_A) - response = self.api.getTestCaseIDByName('dictResult', - testprojectname='NEW_PROJECT_API') - self.assertEqual(dict, type(response)) - self.assertEqual('TESTCASE_B', response['1']['name']) - self.assertEqual(self.api.devKey, self.api.callArgs['devKey']) - - def test_getTestCaseIDByName_listResult(self): - self.api.loadScenario(SCENARIO_A) - response = self.api.getTestCaseIDByName('listResult') - self.assertEqual(list, type(response)) - self.assertEqual('TESTCASE_AA', response[0]['name']) - self.assertEqual(self.api.devKey, self.api.callArgs['devKey']) - - def test_testLinkVersion_beforeTL199(self): - self.api.loadScenario(SCENARIO_TL198) - response = self.api.testLinkVersion() - self.assertEqual('<= 1.9.8', response) - - def test_testLinkVersion_withTL199(self): - self.api.loadScenario(SCENARIO_TL199) - response = self.api.testLinkVersion() - self.assertEqual('1.9.9', response) - - def test_connectionInfo_beforeTL199(self): - self.api.loadScenario(SCENARIO_TL198) - response = self.api.connectionInfo() - self.assertRegex(response, '\d*\.\d*\.\d*') - - def test_getTestCaseCustomFieldDesignValue_notAssigned(self): - self.api.loadScenario(SCENARIO_CUSTOM_FIELDS) - with self.assertRaisesRegex(TLResponseError, '9003.*Custom Field.*not assigned'): - response = self.api.getTestCaseCustomFieldDesignValue('GPROAPI8-2', - 1, '7760', 'cf_notAssigned', details='full') - - def test_getTestCaseCustomFieldDesignValue_full(self): - self.api.loadScenario(SCENARIO_CUSTOM_FIELDS) - response = self.api.getTestCaseCustomFieldDesignValue('GPROAPI8-2', - 1, '7760', 'cf_full', details='full') - self.assertEqual('a custom spec design string', response['value']) - self.assertEqual('1', response['enable_on_design']) - self.assertEqual('0', response['enable_on_testplan_design']) - self.assertEqual('0', response['enable_on_execution']) - - def test_getTestCaseCustomFieldDesignValue_value(self): - self.api.loadScenario(SCENARIO_CUSTOM_FIELDS) - response = self.api.getTestCaseCustomFieldDesignValue('GPROAPI8-2', - 1, '7760', 'cf_value', details='value') - self.assertEqual('a custom spec design string', response) - - def test_getTestCaseCustomFieldDesignValue_valueEmpty(self): - self.api.loadScenario(SCENARIO_CUSTOM_FIELDS) - response = self.api.getTestCaseCustomFieldDesignValue('GPROAPI8-2', - 1, '7760', 'cf_valueEmpty', details='value') - self.assertEqual('', response) - - def test_getTestCaseCustomFieldDesignValue_simple(self): - self.api.loadScenario(SCENARIO_CUSTOM_FIELDS) - response = self.api.getTestCaseCustomFieldDesignValue('GPROAPI8-2', - 1, '7760', 'cf_simple', details='simple') - self.assertEqual('a custom spec design string', response['value']) - - def test_updateTestCaseCustomFieldDesignValue_simple(self): - self.api.loadScenario(SCENARIO_CUSTOM_FIELDS) - response = self.api.updateTestCaseCustomFieldDesignValue('GPROAPI8-2', - 1, '7760', {'cf_field1' : 'a_string'}) - self.assertEqual('', response) - - def test_getTestCaseCustomFieldExecutionValue_notAssigned(self): - self.api.loadScenario(SCENARIO_CUSTOM_FIELDS) - response = self.api.getTestCaseCustomFieldExecutionValue( - 'cf_notAssigned', '7760', 1, '792', '7761') - self.assertEqual(None, response) - self.assertEqual(self.api.devKey, self.api.callArgs['devKey']) - - def test_getTestCaseCustomFieldExecutionValue_full(self): - self.api.loadScenario(SCENARIO_CUSTOM_FIELDS) - response = self.api.getTestCaseCustomFieldExecutionValue( - 'cf_full', '7760', 1, '792', '7761') - self.assertEqual('a custom exec string', response['value']) - self.assertEqual('0', response['enable_on_design']) - self.assertEqual('0', response['enable_on_testplan_design']) - self.assertEqual('1', response['enable_on_execution']) - - def test_getTestCaseCustomFieldTestPlanDesignValue_notAssigned(self): - self.api.loadScenario(SCENARIO_CUSTOM_FIELDS) - response = self.api.getTestCaseCustomFieldTestPlanDesignValue( - 'cf_notAssigned', '7760', 1, '7761', '779') - self.assertEqual(None, response) - self.assertEqual(self.api.devKey, self.api.callArgs['devKey']) - - def test_getTestCaseCustomFieldTestPlanDesignValue_full(self): - self.api.loadScenario(SCENARIO_CUSTOM_FIELDS) - response = self.api.getTestCaseCustomFieldTestPlanDesignValue( - 'cf_full', '7760', 1, '7761', '779') - self.assertEqual('a custom PlanDesign string', response['value']) - self.assertEqual('0', response['enable_on_design']) - self.assertEqual('1', response['enable_on_testplan_design']) - self.assertEqual('0', response['enable_on_execution']) - - def test_getTestSuiteCustomFieldDesignValue_notAssigned(self): - self.api.loadScenario(SCENARIO_CUSTOM_FIELDS) - response = self.api.getTestSuiteCustomFieldDesignValue( - 'cf_notAssigned', '7760', '7762') - self.assertEqual(None, response) - self.assertEqual(self.api.devKey, self.api.callArgs['devKey']) - - def test_getTestSuiteCustomFieldDesignValue_full(self): - self.api.loadScenario(SCENARIO_CUSTOM_FIELDS) - response = self.api.getTestSuiteCustomFieldDesignValue( - 'cf_full', '7760', '7762') - self.assertEqual('a custom TSuite string', response['value']) - self.assertEqual('1', response['enable_on_design']) - self.assertEqual('0', response['enable_on_testplan_design']) - self.assertEqual('0', response['enable_on_execution']) - - def test_getTestPlanCustomFieldDesignValue_notAssigned(self): - self.api.loadScenario(SCENARIO_CUSTOM_FIELDS) - response = self.api.getTestPlanCustomFieldDesignValue( - 'cf_notAssigned', '7760', '7761') - self.assertEqual(None, response) - self.assertEqual(self.api.devKey, self.api.callArgs['devKey']) - - def test_getTestPlanCustomFieldDesignValue_full(self): - self.api.loadScenario(SCENARIO_CUSTOM_FIELDS) - response = self.api.getTestPlanCustomFieldDesignValue( - 'cf_full', '7760', '7761') - self.assertEqual('a custom TPlan string', response['value']) - self.assertEqual('1', response['enable_on_design']) - self.assertEqual('0', response['enable_on_testplan_design']) - self.assertEqual('0', response['enable_on_execution']) - - def test_getReqSpecCustomFieldDesignValue_notAssigned(self): - self.api.loadScenario(SCENARIO_CUSTOM_FIELDS) - response = self.api.getReqSpecCustomFieldDesignValue( - 'cf_notAssigned', '7760', '7789') - self.assertEqual(None, response) - self.assertEqual(self.api.devKey, self.api.callArgs['devKey']) - - def test_getReqSpecCustomFieldDesignValue_full(self): - self.api.loadScenario(SCENARIO_CUSTOM_FIELDS) - response = self.api.getReqSpecCustomFieldDesignValue( - 'cf_full', '7760', '7789') - self.assertEqual('a custom ReqSpec string', response['value']) - self.assertEqual('1', response['enable_on_design']) - self.assertEqual('0', response['enable_on_testplan_design']) - self.assertEqual('0', response['enable_on_execution']) - - def test_getRequirementCustomFieldDesignValue_notAssigned(self): - self.api.loadScenario(SCENARIO_CUSTOM_FIELDS) - response = self.api.getRequirementCustomFieldDesignValue( - 'cf_notAssigned', '7760', '7791') - self.assertEqual(None, response) - self.assertEqual(self.api.devKey, self.api.callArgs['devKey']) - - def test_getRequirementCustomFieldDesignValue_full(self): - self.api.loadScenario(SCENARIO_CUSTOM_FIELDS) - response = self.api.getRequirementCustomFieldDesignValue( - 'cf_full', '7760', '7791') - self.assertEqual('a custom Req string', response['value']) - self.assertEqual('1', response['enable_on_design']) - self.assertEqual('0', response['enable_on_testplan_design']) - self.assertEqual('0', response['enable_on_execution']) - - def test_reportTCResult_user(self): - self.api.loadScenario(SCENARIO_A) - response = self.api.reportTCResult(4712, 'p', testcaseid=4711, - buildname='build 4713', notes='note 4714', - user='a login name') - self.assertEqual('reportTCResult', response[0]['operation']) - self.assertEqual(self.api.devKey, self.api.callArgs['devKey']) - self.assertEqual('a login name', self.api.callArgs['user']) - - def test_whatArgs_reportTCResult(self): - argsDescription = self.api.whatArgs('reportTCResult') - self.assertIn('user=<user>', argsDescription) - self.assertIn('execduration=<execduration>', argsDescription) - self.assertIn('timestamp=<timestamp>', argsDescription) - - def test_whatArgs_getLastExecutionResult(self): - argsDescription = self.api.whatArgs('getLastExecutionResult') - self.assertIn('options=<options>', argsDescription) - self.assertIn('getBugs', argsDescription) - - def test__getAttachmentArgs_textfile(self): - "py3 issue #39 TypeError: expected bytes-like object, not str" - # under py2, on windows text files should be open with 'r' mode and - # binary files with 'rb' - # see http://docs.python.org/2/tutorial/inputoutput.html#reading-and-writing-files - # under py3, text files open with 'r' on windows makes problem - # see https://github.com/lczub/TestLink-API-Python-client/issues/39 - a_file=open(ATTACHMENT_EXAMPLE_TEXT) - args = self.api._getAttachmentArgs(a_file) - # repeating this test failed in second run, cause filename is then - # 'testlinkapigeneric_offline_test.pyc' - self.assertEqual('testlinkapigeneric_offline_test.py', args['filename']) - # filetype is also OS depended, either 'text/plain' or 'text/x-python' - self.assertIn('text/', args['filetype']) - self.assertIsNotNone(args['content']) - - def test__getAttachmentArgs_filepath(self): - "enhancement #40 handle file patch instead file object" - args = self.api._getAttachmentArgs(ATTACHMENT_EXAMPLE_TEXT) - self.assertEqual('testlinkapigeneric_offline_test.py', args['filename']) - # filetype is also OS depended, either 'text/plain' or 'text/x-python' - self.assertIn('text/', args['filetype']) - self.assertIsNotNone(args['content']) - - def test___str__pyversion(self): - self.api.loadScenario(SCENARIO_TL199) - api_info = self.api.__str__() - py_info = '(PY %i.' % sys.version_info[0] - self.assertIn(py_info, api_info) - - def test_getProjectKeywords_noKeywords(self): - self.api.loadScenario(SCENARIO_A) - response = self.api.getProjectKeywords('noKeyword') - self.assertEqual({}, response) - self.assertEqual(self.api.devKey, self.api.callArgs['devKey']) - - def test_getProjectKeywords_twoKeywords(self): - self.api.loadScenario(SCENARIO_A) - response = self.api.getProjectKeywords('twoKeywords') - self.assertEqual('KeyWord01', response['25']) - self.assertEqual('KeyWord02', response['26']) - - def test_getTestCaseKeywords_noKeywords(self): - self.api.loadScenario(SCENARIO_A) - response = self.api.getTestCaseKeywords(testcaseid='noKeyword') - self.assertEqual({}, response) - self.assertEqual(self.api.devKey, self.api.callArgs['devKey']) - - def test_getProjectKeywords_twoKeywords(self): - self.api.loadScenario(SCENARIO_A) - response = self.api.getTestCaseKeywords(testcaseid='twoKeywords') - self.assertEqual('KeyWord01', response['25']) - self.assertEqual('KeyWord02', response['26']) - - def test_whatArgs_getTestCasesForTestPlan(self): - argsDescription = self.api.whatArgs('getTestCasesForTestPlan') - self.assertIn('buildid=<buildid>', argsDescription) - self.assertIn('platformid=<platformid>', argsDescription) - self.assertIn('keywordid - keywords', argsDescription) - - def test_whatArgs_createTestCase(self): - argsDescription = self.api.whatArgs('createTestCase') - self.assertIn('executiontype=<executiontype>', argsDescription) - - def test_getProjects_noProject(self): - self.api.loadScenario(SCENARIO_NO_PROJECT) - response = self.api.getProjects() - self.assertEqual([], response) - self.assertEqual(self.api.devKey, self.api.callArgs['devKey']) - - def test_connect_with_proxy(self): - """ create a TestLink Generic API dummy with ProxiedTransport""" - self.api = DummyAPIGeneric('http://SERVER-URL-71', 'DEVKEY-71', - transport='PROXY-71') - if not IS_PY26: - # Py 26 does not define a __call__ method and getattr is overriden - # to created a request and return the response - # -> so no access to attribute __transport with Py26 - self.assertEqual('PROXY-71', self.api.server.__call__('transport')) - - def test_whatArgs_createTestPlan(self): - argsDescription = self.api.whatArgs('createTestPlan') - self.assertIn('prefix=<prefix>', argsDescription) - self.assertIn('testprojectname=<testprojectname>', argsDescription) - -if __name__ == "__main__": - #import sys;sys.argv = ['', 'Test.testName'] +#! /usr/bin/python +# -*- coding: UTF-8 -*- + +# Copyright 2013-2019 Luiko Czub, TestLink-API-Python-client developers +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------ + +# this test works WITHOUT an online TestLink Server +# no calls are send to a TestLink Server + +import sys, os.path +import unittest + +if sys.version_info[0] == 2 and sys.version_info[1] == 7: + # py27 and py31 assertRaisesRegexp was renamed in py32 to assertRaisesRegex + unittest.TestCase.assertRaisesRegex = unittest.TestCase.assertRaisesRegexp + # py27 and py31 assertRegexpMatches was renamed in py32 to assertRegex + unittest.TestCase.assertRegex = unittest.TestCase.assertRegexpMatches + + +from testlink import TestlinkAPIGeneric, TestLinkHelper +from testlink.testlinkerrors import TLArgError, TLResponseError, TLAPIError + + +#from testlink.testlinkapigeneric import positionalArgNamesDefault +# scenario_a includes response from a testlink 1.9.8 server +SCENARIO_A = {'repeat' : 'You said: One World', + 'sayHello' : 'Hey Folks!', + 'doesUserExist' : { + 'Big Bird' : [{'message': '(doesUserExist) - Cannot Find User Login provided (Big Bird).', + 'code': 10000}], + 'admin' : True }, + 'getProjectTestPlans' : { + 'onePlan' : [{'name': 'TestPlan_API', + 'notes': 'New TestPlan created with the API', + 'active': '1', 'is_public': '1', + 'testproject_id': '21', 'id': '22'}] , + 'noPlan' : '' }, + 'getBuildsForTestPlan' : {'noBuild' : '' }, + 'getTestPlanPlatforms' : { + 'twoPlatforms' : [{'notes': '', 'id': '1', 'name': 'dutch'}, + {'notes': '', 'id': '2', 'name': 'platt'}], + 'noPlatform' : [{'message': 'Test plan (noPlatform) has no platforms linked', + 'code': 3041}]}, + 'getTestSuitesForTestPlan' : {'noSuite' : ''}, + 'getTestSuitesForTestSuite' : {'noSuite' : ''}, + 'getFirstLevelTestSuitesForTestProject' : { + 'noSuite' : [{'message': 'Test Project (noSuite) is empty.', + 'code': 7008}]}, + 'getTestCasesForTestSuite' : {'noTestCase' : [] }, + 'getTestCasesForTestPlan' : {'noTestCase' : [] }, + 'getTestCaseIDByName' : { + 'dictResult' : {'1': {'parent_id': '24', 'tc_external_id': '2', + 'id': '33', 'tsuite_name': 'B - First Level', + 'name': 'TESTCASE_B'}}, + 'listResult' : [{'parent_id': '25', 'tc_external_id': '1', + 'id': '26', 'tsuite_name': 'AA - Second Level', + 'name': 'TESTCASE_AA'}]}, + 'getProjectPlatforms' : { + 'twoPlatforms' : {'dutch' : {'id': '1', 'name': 'dutch'}, + 'platt' : {'id': '2', 'name': 'platt'}}, + 'noPlatform' : {} + }, + 'reportTCResult' : [{'status': True, 'operation': 'reportTCResult', + 'message': 'Success!', 'overwrite': False, 'id': '773'}], + 'getProjectKeywords' : { + 'twoKeywords' : {'25': 'KeyWord01', '26': 'KeyWord02'}, + 'noKeyword' : {} + }, + 'getTestCaseKeywords' : { + 'twoKeywords' : {'25': 'KeyWord01', '26': 'KeyWord02'}, + 'noKeyword' : {} + } + } + +# scenario_tl198 used by test with older responses, changed in TL 1.9.9 +SCENARIO_TL198 = {'testLinkVersion' : 'unknown', + 'about' : 'Testlink API Version: 1.0 ...'} + +# scenario_tl199 used by test with newer responses, changed in TL 1.9.9 +SCENARIO_TL199 = {'testLinkVersion' : '1.9.9', + 'about' : 'Testlink API Version: 1.0 ...'} + +# scenario_custom_fields defines response for custom field request +# {'default_value': '', 'enable_on_execution': '1', 'name': 'cf_tc_ex_string', 'location': '1', 'enable_on_design': '0', 'valid_regexp': '', 'length_min': '0', 'possible_values': '', 'value': 'a custom string', 'label': 'CF Exec String', 'show_on_testplan_design': '0', 'display_order': '1', 'length_max': '0', 'show_on_design': '0', 'required': '0', 'show_on_execution': '1', 'type': '0', 'id': '24', 'node_id': '7691', 'enable_on_testplan_design': '0'} +SCENARIO_CUSTOM_FIELDS = { + 'getTestCaseCustomFieldDesignValue' : { + 'cf_notAssigned' : [{'message': '(getTestCaseCustomFieldDesignValue) - Custom Field (name:cf_tc_sd_string), is not assigned to Test Project(name=PROJECT_API_GENERIC-8 / id=7760)', + 'code': 9003}], + 'cf_full' : {'default_value': '', 'enable_on_execution': '0', 'name': 'cf_tc_sd_string', + 'location': '1', 'enable_on_design': '1', 'valid_regexp': '', 'length_min': '0', 'possible_values': '', + 'value': 'a custom spec design string', 'label': 'CF SpecDesign String', 'show_on_testplan_design': '0', + 'display_order': '1', 'length_max': '0', 'show_on_design': '1', 'required': '0', 'show_on_execution': '1', + 'type': '0', 'id': '22', 'node_id': '7691', 'enable_on_testplan_design': '0'}, + 'cf_value' : 'a custom spec design string', + 'cf_valueEmpty' : '', + 'cf_simple' : {'type': '0', 'name': 'cf_tc_sd_string', + 'value': 'a custom spec design string', 'label': 'CF SpecDesign String'} + }, + 'updateTestCaseCustomFieldDesignValue' : { + 'cf_notAssigned' : '', + 'a_string' : '' + }, + + 'getTestCaseCustomFieldExecutionValue' : { + 'cf_notAssigned' : '', + 'cf_full' : {'default_value': '', 'enable_on_execution': '1', 'name': 'cf_tc_ex_string', + 'location': '1', 'enable_on_design': '0', 'valid_regexp': '', 'length_min': '0', 'possible_values': '', + 'value': 'a custom exec string', 'label': 'CF Exec String', 'show_on_testplan_design': '0', + 'display_order': '1', 'length_max': '0', 'show_on_design': '0', 'required': '0', 'show_on_execution': '1', + 'type': '0', 'id': '24', 'node_id': '7691', 'enable_on_testplan_design': '0'} + }, + + 'getTestCaseCustomFieldTestPlanDesignValue' : { + 'cf_notAssigned' : '', + 'cf_full' : {'default_value': '', 'enable_on_execution': '0', 'name': 'cf_tc_pd_string', + 'enable_on_design': '0', 'valid_regexp': '', 'length_min': '0', 'possible_values': '', + 'value': 'a custom PlanDesign string', 'label': 'CF PlanDesign String', 'show_on_testplan_design': '1', + 'display_order': '1', 'length_max': '0', 'show_on_design': '0', 'required': '0', 'show_on_execution': '1', + 'type': '0', 'id': '28', 'node_id': '779', 'enable_on_testplan_design': '1'} + }, + 'getTestSuiteCustomFieldDesignValue' : { + 'cf_notAssigned' : '', + 'cf_full' : {'default_value': '', 'enable_on_execution': '0', 'name': 'cf_ts_string', 'location': '1', + 'enable_on_design': '1', 'valid_regexp': '', 'length_min': '0', 'possible_values': '', + 'value': 'a custom TSuite string', 'label': 'CF TestSuite String', 'show_on_testplan_design': '0', + 'display_order': '1', 'length_max': '0', 'show_on_design': '1', 'required': '0', 'show_on_execution': '1', + 'type': '0', 'id': '30', 'node_id': '', 'enable_on_testplan_design': '0'} + }, + + 'getTestPlanCustomFieldDesignValue' : { + 'cf_notAssigned' : '', + 'cf_full' : {'default_value': '', 'enable_on_execution': '0', 'name': 'cf_tp_string', 'location': '1', + 'enable_on_design': '1', 'valid_regexp': '', 'length_min': '0', 'possible_values': '', + 'value': 'a custom TPlan string', 'label': 'CF TPlan String', 'show_on_testplan_design': '0', + 'display_order': '1', 'length_max': '0', 'show_on_design': '1', 'required': '0', 'show_on_execution': '1', + 'type': '0', 'id': '31', 'node_id': '', 'enable_on_testplan_design': '0'} + }, + + 'getReqSpecCustomFieldDesignValue' : { + 'cf_notAssigned' : '', + 'cf_full' : {'default_value': '', 'enable_on_execution': '0', 'name': 'cf_req_sd_string', 'location': '1', + 'enable_on_design': '1', 'valid_regexp': '', 'length_min': '0', 'possible_values': '', + 'value': 'a custom ReqSpec string', 'label': 'CF ReqSpec String', 'show_on_testplan_design': '0', + 'display_order': '1', 'length_max': '0', 'show_on_design': '1', 'required': '0', 'show_on_execution': '0', + 'type': '0', 'id': '32', 'node_id': '', 'enable_on_testplan_design': '0'} + }, + 'getRequirementCustomFieldDesignValue' : { + 'cf_notAssigned' : '', + 'cf_full' : {'default_value': '', 'enable_on_execution': '0', 'name': 'cf_req_string', 'location': '1', + 'enable_on_design': '1', 'valid_regexp': '', 'length_min': '0', 'possible_values': '', + 'value': 'a custom Req string', 'label': 'CF Req String', 'show_on_testplan_design': '0', + 'display_order': '1', 'length_max': '0', 'show_on_design': '1', 'required': '0', 'show_on_execution': '0', + 'type': '0', 'id': '33', 'node_id': '', 'enable_on_testplan_design': '0'} + } + + } + +# scenario_keywords defines response with keywords +SCENARIO_KEYWORDS = {'getTestCasesForTestSuite' : { + 'noTestCase' : [] , + 'keyWords' : [{'node_order': '0', 'is_open': '1', + 'keywords': {'1': {'keyword_id': '1', 'notes': 'a key word', 'testcase_id': '8144', 'keyword': 'KeyWord01'}, + '3': {'keyword_id': '3', 'notes': 'a third key word', 'testcase_id': '8144', 'keyword': 'KeyWord03'}}, + 'id': '8144', 'node_type_id': '3', 'layout': '1', 'tc_external_id': '2', 'parent_id': '8134', + 'version': '1', 'estimated_exec_duration': '3.00', 'updater_id': '2', 'status': '1', + 'tsuite_name': 'B - First Level', 'importance': '3', 'modification_ts': '2014-06-30 20:45:40', + 'execution_type': '1', 'preconditions': '<p>\n\tthese are the preconditions</p>\n', 'active': '1', + 'creation_ts': '2014-06-28 22:06:17', 'node_table': 'testcases', 'tcversion_id': '8145', + 'name': 'TESTCASE_B', 'summary': '<p>\n\tThis is the summary of the Test Case B</p>\n', + 'steps': [{'step_number': '1', 'actions': 'Step action 1 -b ', 'execution_type': '2', 'active': '1', 'id': '8151', 'expected_results': 'Step result 1 - b'}, + {'step_number': '2', 'actions': 'Step action 2 -b ', 'execution_type': '2', 'active': '1', 'id': '8152', 'expected_results': 'Step result 2 - b'}, + {'step_number': '3', 'actions': 'action 3 createTestCaseSteps.update', 'execution_type': '2', 'active': '1', 'id': '8153', 'expected_results': 'update - cause step 3 already exist'}, + {'step_number': '4', 'actions': 'Step action 4 -b ', 'execution_type': '2', 'active': '1', 'id': '8154', 'expected_results': 'Step result 4 - b'}, + {'step_number': '5', 'actions': 'Step action 5 -b changed by updateTestCase', 'execution_type': '2', 'active': '1', 'id': '8155', 'expected_results': 'Step result 5 - b changed'}, + {'step_number': '6', 'actions': 'Step action 6 -b added by updateTestCase', 'execution_type': '2', 'active': '1', 'id': '8156', 'expected_results': 'Step result 6 - b added'}, + {'step_number': '7', 'actions': 'action 7 createTestCaseSteps.create', 'execution_type': '2', 'active': '1', 'id': '8157', 'expected_results': 'create - cause step 7 not yet exist'}, + {'step_number': '8', 'actions': 'action 8 createTestCaseSteps.update', 'execution_type': '2', 'active': '1', 'id': '8158', 'expected_results': 'create - cause step 8 not yet exist'}], + 'author_id': '1', 'external_id': 'GPROAPI10-2'}] + } + } + +# scenario_no_project simulates a fresh empty test link application +SCENARIO_NO_PROJECT = {'getProjects' : [] } + +# scenario_requirements defines response with requirements +SCENARIO_REQUIREMENTS = { + 'getRequirements' : { + 'allReqs' : [{'id': '15344', 'req_doc_id': 'use-case-01'}, + {'id': '15346', 'req_doc_id': 'none-function-01'}, + {'id': '15350', 'req_doc_id': 'restriction-01'}, + {'id': '15352', 'req_doc_id': 'user-gui-01'}, + {'id': '15356', 'req_doc_id': 'system-func-01'}, + {'id': '15358', 'req_doc_id': 'feature-01'}], + 'noReqs' : [], + 'with_srs_id' : [{'id': '15344', 'req_doc_id': 'use-case-01', 'srs_id': '15342'}, + {'id': '15346', 'req_doc_id': 'none-function-01', 'srs_id': '15342'}, + {'id': '15350', 'req_doc_id': 'restriction-01', 'srs_id': '15348'}, + {'id': '15352', 'req_doc_id': 'user-gui-01', 'srs_id': '15348'}, + {'id': '15356', 'req_doc_id': 'system-func-01', 'srs_id': '15354'}, + {'id': '15358', 'req_doc_id': 'feature-01', 'srs_id': '15354'}] + }, + 'getReqCoverage' : { + 'reqCovered' : [{'id': '15326', 'name': 'TESTCASE_B', 'tc_external_id': '2', + 'login': 'pyTLapi', 'creation_ts': '2017-04-05 20:55:51'}], + 'reqNotCovered' : '', + 'reqUnknown' : [{'code': 11004, + 'message': '(getReqCoverage) - Requirement (docid=req_doc_unknown) does not belong to project (id=15312).'}] + } + } + + +# example text file attachment = this python file +# why not using os.path.realpath(__file__) +# -> cause __file__ could be compiled python file *.pyc, if the test run is +# repeated without changing the test code +ATTACHMENT_EXAMPLE_TEXT= os.path.join(os.path.dirname(__file__), + 'testlinkapigeneric_offline_test.py') + + +class DummyAPIGeneric(TestlinkAPIGeneric): + """ Dummy for Simulation TestLinkAPIGeneric. + Overrides + - _callServer() Method to return test scenarios + - extend positional_arg_names for method 'DummyMethod' + """ + + __slots__ = ['scenario_data', 'callArgs'] + + def __init__(self, server_url, devKey, **args): + super(DummyAPIGeneric, self).__init__(server_url, devKey, **args) + self._positionalArgNames['DummyMethod'] = ['Uno', 'due', 'tre'] + self.scenario_data = {} + self.callArgs = None + + + def loadScenario(self, a_scenario): + self.scenario_data = a_scenario + self.callArgs = None + + def _callServer(self, methodAPI, argsAPI=None): + self.callArgs = argsAPI + response = None + if methodAPI in ['DummyMethod']: + response = [argsAPI] + else: + data = self.scenario_data[methodAPI] + if methodAPI in ['doesUserExist']: + response = data[argsAPI['user']] + elif methodAPI in ['getProjectTestPlans', 'getProjectPlatforms', + 'getFirstLevelTestSuitesForTestProject', + 'getProjectKeywords', 'getRequirements', + 'getReqCoverage']: + response = data[argsAPI['testprojectid']] + elif methodAPI in ['getBuildsForTestPlan', 'getTestPlanPlatforms', + 'getTestSuitesForTestPlan', 'getTestCasesForTestPlan']: + response = data[argsAPI['testplanid']] + elif methodAPI in ['getTestSuitesForTestSuite', + 'getTestCasesForTestSuite']: + response = data[argsAPI['testsuiteid']] + elif methodAPI in ['getTestCaseIDByName']: + response = data[argsAPI['testcasename']] + elif methodAPI in ['getTestCaseKeywords']: + response = data[argsAPI['testcaseid']] + elif methodAPI in ['testLinkVersion']: + response = data + if data == 'unknown': + raise TLAPIError('problems calling the API method testLinkVersion1') + elif methodAPI == 'updateTestCaseCustomFieldDesignValue': + response = data[argsAPI['customfields']['cf_field1']] + elif 'CustomField' in methodAPI: + response = data[argsAPI['customfieldname']] + else: + response = data + return response + + +class TestLinkAPIGenericOfflineTestCase(unittest.TestCase): + """ TestCases for TestlinkAPIGeneric - does not interacts with a TestLink Server. + works with DummyAPIGeneric which returns special test data + """ + + @classmethod + def setUpClass(cls): + cls.api = TestLinkHelper().connect(DummyAPIGeneric) + +# def tearDown(self): +# pass + + + def test_convertPositionalArgs(self): + response = self.api._convertPostionalArgs('DummyMethod', [1,2,3]) + self.assertEqual({'Uno' : 1, 'due' :2, 'tre' : 3}, response) + + def test__convertPositionalArgs_missingConf(self): + client = self.api + def a_func(a_api): a_api._convertPostionalArgs('NoConfigMethod', [1,2]) + self.assertRaises(TLArgError, a_func, client) + + def test__convertPositionalArgs_lessValues(self): + client = self.api + def a_func(a_api): a_api._convertPostionalArgs('DummyMethod', [1,2]) + self.assertRaises(TLArgError, a_func, client) + + def test__convertPositionalArgs_moreValues(self): + client = self.api + def a_func(a_api): a_api._convertPostionalArgs('DummyMethod', [1,2,3,4]) + self.assertRaises(TLArgError, a_func, client) + + def test_callServerWithPosArgs_pos(self): + self.api.callServerWithPosArgs('DummyMethod', 1,2,3) + self.assertEqual({'Uno' : 1, 'due' :2, 'tre' : 3}, self.api.callArgs) + + def test_callServerWithPosArgs_pos_opt(self): + self.api.callServerWithPosArgs('DummyMethod', 1,2,3, quad=4) + self.assertEqual({'Uno' : 1, 'due' :2, 'tre' : 3, 'quad' : 4}, self.api.callArgs) + + def test_callServerWithPosArgs_opt(self): + self.api.callServerWithPosArgs('DummyMethod', quad=4) + self.assertEqual({'quad' : 4}, self.api.callArgs) + + def test_callServerWithPosArgs_none(self): + self.api.callServerWithPosArgs('DummyMethod') + self.assertEqual({}, self.api.callArgs) + + def test_checkResponse_emptyResponse(self): + client = self.api + def a_func(a_api, response): + a_api._checkResponse(response, 'DummyMethod', + {'Uno' : 1, 'due' :2, 'tre' : 3}) + self.assertRaises(TLResponseError, a_func, client, '') + self.assertRaises(TLResponseError, a_func, client, []) + + def test_checkResponse_errorResponse(self): + client = self.api + responseA = [{'message': '(reportTCResult) - TC ID 709 does not exist!', + 'code': 5000}] + def a_func(a_api, response): + a_api._checkResponse(response, 'DummyMethod', + {'Uno' : 1, 'due' :2, 'tre' : 3}) + self.assertRaises(TLResponseError, a_func, client, responseA) + + def test_checkResponse_okResponse(self): + self.api._checkResponse( + [{'message': 'all fine, cause no key with name code'}], + 'DummyMethod', {'Uno' : 1, 'due' :2, 'tre' : 3}) + self.api._checkResponse( + 'some API Call juts returns one string without codes', + 'DummyMethod', {'Uno' : 1, 'due' :2, 'tre' : 3}) + + def test_checkResponse_booleanResponse(self): + response = True + self.api._checkResponse(response, 'DummyMethod', + {'Uno' : 1, 'due' :2, 'tre' : 3}) + + def test_checkResponse_dictionaryResponse(self): + response = {'note' : 'uploadAttachment Calls return {..} and not [{..}]'} + self.api._checkResponse(response, 'DummyMethod', + {'Uno' : 1, 'due' :2, 'tre' : 3}) + + def test_checkResponse_errorResponse_sringCode(self): + client = self.api + + responseA = [{'message': '(getUserByID) - Cannot Find User with DB ID (4711).', + 'code': 'NO_USER_BY_ID_LOGIN'}] + def a_func(a_api, response): + a_api._checkResponse(response, 'getUserByID', + {'userid' : 4711}) + self.assertRaises(TLResponseError, a_func, client, responseA) + + def test__apiMethodArgNames_noArgs(self): + response = self.api._apiMethodArgNames('sayHello') + self.assertEqual(response, ([], [], [])) + + def test__apiMethodArgNames_onlyOptionalArgs(self): + response = self.api._apiMethodArgNames('getTestCaseAttachments') + self.assertEqual(response[0], []) + self.assertGreater(len(response[1]), 0) + self.assertEqual(response[2], []) + + def test__apiMethodArgNames__OptionalAndPositionalArgs(self): + response = self.api._apiMethodArgNames('createBuild') + self.assertGreater(len(response[0]), 0) + self.assertGreater(len(response[1]), 0) + self.assertEqual(response[2], []) + + def test__apiMethodArgNames__MandatoryArgs(self): + response = self.api._apiMethodArgNames('uploadExecutionAttachment') + self.assertGreater(len(response[0]), 0) + self.assertGreater(len(response[1]), 0) + self.assertGreater(len(response[2]), 0) + + def test_noWrapperName_apiMethods(self): + " decorator test: API Methods internal function name should be unchanged " + + # apiMethod with decorator @decoApiCallWithoutArgs + self.assertEqual('sayHello', self.api.sayHello.__name__) + # apiMethod with decorator @decoApiCallWithArgs + self.assertEqual('repeat', self.api.repeat.__name__) + # apiMethod with decorator @decoApiCallAddDevKey + self.assertEqual('createBuild', self.api.createBuild.__name__) + # apiMethod with decorator @decoMakerApiCallReplaceTLResponseError() + self.assertEqual('getProjectTestPlans', self.api.getProjectTestPlans.__name__) + # apiMethod with decorator @decoApiCallAddAttachment + self.assertEqual('uploadExecutionAttachment', self.api.uploadExecutionAttachment.__name__) + + def test_ping(self): + self.api.loadScenario(SCENARIO_A) + response = self.api.ping() + self.assertEqual('Hey Folks!', response) + + + def test_getProjectTestPlans_noPlan(self): + self.api.loadScenario(SCENARIO_A) + response = self.api.getProjectTestPlans('noPlan') + self.assertEqual([], response) + self.assertEqual(self.api.devKey, self.api.callArgs['devKey']) + + def test_getProjectTestPlans_onePlan(self): + self.api.loadScenario(SCENARIO_A) + response = self.api.getProjectTestPlans('onePlan') + self.assertEqual('21', response[0]['testproject_id']) + self.assertEqual(1, len(response)) + + def test_getProjectPlatforms_noPlatform(self): + self.api.loadScenario(SCENARIO_A) + response = self.api.getProjectPlatforms('noPlatform') + self.assertEqual({}, response) + self.assertEqual(self.api.devKey, self.api.callArgs['devKey']) + + def test_getProjectPlatforms_twoPlatforms(self): + self.api.loadScenario(SCENARIO_A) + response = self.api.getProjectPlatforms('twoPlatforms') + self.assertEqual('1', response['dutch']['id']) + self.assertEqual(2, len(response)) + + + def test_getBuildsForTestPlan_noBuild(self): + self.api.loadScenario(SCENARIO_A) + response = self.api.getBuildsForTestPlan('noBuild') + self.assertEqual([], response) + self.assertEqual(self.api.devKey, self.api.callArgs['devKey']) + + def test_getTestPlanPlatforms_noPlatform(self): + self.api.loadScenario(SCENARIO_A) + response = self.api.getTestPlanPlatforms('noPlatform') + self.assertEqual([], response) + self.assertEqual(self.api.devKey, self.api.callArgs['devKey']) + + def test_getTestPlanPlatforms_twoPlatforms(self): + self.api.loadScenario(SCENARIO_A) + response = self.api.getTestPlanPlatforms('twoPlatforms') + self.assertEqual('dutch', response[0]['name']) + self.assertEqual(2, len(response)) + + def test_getTestSuitesForTestPlan_noSuite(self): + self.api.loadScenario(SCENARIO_A) + response = self.api.getTestSuitesForTestPlan('noSuite') + self.assertEqual([], response) + self.assertEqual(self.api.devKey, self.api.callArgs['devKey']) + + def test_getTestSuitesForTestSuite_noSuite(self): + self.api.loadScenario(SCENARIO_A) + response = self.api.getTestSuitesForTestSuite('noSuite') + self.assertEqual([], response) + self.assertEqual(self.api.devKey, self.api.callArgs['devKey']) + + def test_getFirstLevelTestSuitesForTestProject_noSuite(self): + self.api.loadScenario(SCENARIO_A) + response = self.api.getFirstLevelTestSuitesForTestProject('noSuite') + self.assertEqual([], response) + self.assertEqual(self.api.devKey, self.api.callArgs['devKey']) + + def test_getTestCasesForTestSuite_noTestCase(self): + self.api.loadScenario(SCENARIO_A) + response = self.api.getTestCasesForTestSuite('noTestCase') + self.assertEqual([], response) + self.assertEqual(self.api.devKey, self.api.callArgs['devKey']) + + def test_getTestCasesForTestSuite_keyWords(self): + self.api.loadScenario(SCENARIO_KEYWORDS) + response = self.api.getTestCasesForTestSuite('keyWords', details='full', + getkeywords=True) + self.assertIn('keywords', response[0]) + self.assertEqual(self.api.devKey, self.api.callArgs['devKey']) + + def test_getTestCasesForTestPlan_noTestCase(self): + self.api.loadScenario(SCENARIO_A) + response = self.api.getTestCasesForTestPlan('noTestCase') + self.assertEqual([], response) + self.assertEqual(self.api.devKey, self.api.callArgs['devKey']) + + def test_getTestCaseIDByName_dictResult(self): + self.api.loadScenario(SCENARIO_A) + response = self.api.getTestCaseIDByName('dictResult', + testprojectname='NEW_PROJECT_API') + self.assertEqual(dict, type(response)) + self.assertEqual('TESTCASE_B', response['1']['name']) + self.assertEqual(self.api.devKey, self.api.callArgs['devKey']) + + def test_getTestCaseIDByName_listResult(self): + self.api.loadScenario(SCENARIO_A) + response = self.api.getTestCaseIDByName('listResult') + self.assertEqual(list, type(response)) + self.assertEqual('TESTCASE_AA', response[0]['name']) + self.assertEqual(self.api.devKey, self.api.callArgs['devKey']) + + def test_testLinkVersion_beforeTL199(self): + self.api.loadScenario(SCENARIO_TL198) + response = self.api.testLinkVersion() + self.assertEqual('<= 1.9.8', response) + + def test_testLinkVersion_withTL199(self): + self.api.loadScenario(SCENARIO_TL199) + response = self.api.testLinkVersion() + self.assertEqual('1.9.9', response) + + def test_connectionInfo_beforeTL199(self): + self.api.loadScenario(SCENARIO_TL198) + response = self.api.connectionInfo() + self.assertRegex(response, r'\d*\.\d*\.\d*') + + def test_getTestCaseCustomFieldDesignValue_notAssigned(self): + self.api.loadScenario(SCENARIO_CUSTOM_FIELDS) + with self.assertRaisesRegex(TLResponseError, '9003.*Custom Field.*not assigned'): + response = self.api.getTestCaseCustomFieldDesignValue('GPROAPI8-2', + 1, '7760', 'cf_notAssigned', details='full') + + def test_getTestCaseCustomFieldDesignValue_full(self): + self.api.loadScenario(SCENARIO_CUSTOM_FIELDS) + response = self.api.getTestCaseCustomFieldDesignValue('GPROAPI8-2', + 1, '7760', 'cf_full', details='full') + self.assertEqual('a custom spec design string', response['value']) + self.assertEqual('1', response['enable_on_design']) + self.assertEqual('0', response['enable_on_testplan_design']) + self.assertEqual('0', response['enable_on_execution']) + + def test_getTestCaseCustomFieldDesignValue_value(self): + self.api.loadScenario(SCENARIO_CUSTOM_FIELDS) + response = self.api.getTestCaseCustomFieldDesignValue('GPROAPI8-2', + 1, '7760', 'cf_value', details='value') + self.assertEqual('a custom spec design string', response) + + def test_getTestCaseCustomFieldDesignValue_valueEmpty(self): + self.api.loadScenario(SCENARIO_CUSTOM_FIELDS) + response = self.api.getTestCaseCustomFieldDesignValue('GPROAPI8-2', + 1, '7760', 'cf_valueEmpty', details='value') + self.assertEqual('', response) + + def test_getTestCaseCustomFieldDesignValue_simple(self): + self.api.loadScenario(SCENARIO_CUSTOM_FIELDS) + response = self.api.getTestCaseCustomFieldDesignValue('GPROAPI8-2', + 1, '7760', 'cf_simple', details='simple') + self.assertEqual('a custom spec design string', response['value']) + + def test_updateTestCaseCustomFieldDesignValue_simple(self): + self.api.loadScenario(SCENARIO_CUSTOM_FIELDS) + response = self.api.updateTestCaseCustomFieldDesignValue('GPROAPI8-2', + 1, '7760', {'cf_field1' : 'a_string'}) + self.assertEqual('', response) + + def test_getTestCaseCustomFieldExecutionValue_notAssigned(self): + self.api.loadScenario(SCENARIO_CUSTOM_FIELDS) + response = self.api.getTestCaseCustomFieldExecutionValue( + 'cf_notAssigned', '7760', 1, '792', '7761') + self.assertEqual(None, response) + self.assertEqual(self.api.devKey, self.api.callArgs['devKey']) + + def test_getTestCaseCustomFieldExecutionValue_full(self): + self.api.loadScenario(SCENARIO_CUSTOM_FIELDS) + response = self.api.getTestCaseCustomFieldExecutionValue( + 'cf_full', '7760', 1, '792', '7761') + self.assertEqual('a custom exec string', response['value']) + self.assertEqual('0', response['enable_on_design']) + self.assertEqual('0', response['enable_on_testplan_design']) + self.assertEqual('1', response['enable_on_execution']) + + def test_getTestCaseCustomFieldTestPlanDesignValue_notAssigned(self): + self.api.loadScenario(SCENARIO_CUSTOM_FIELDS) + response = self.api.getTestCaseCustomFieldTestPlanDesignValue( + 'cf_notAssigned', '7760', 1, '7761', '779') + self.assertEqual(None, response) + self.assertEqual(self.api.devKey, self.api.callArgs['devKey']) + + def test_getTestCaseCustomFieldTestPlanDesignValue_full(self): + self.api.loadScenario(SCENARIO_CUSTOM_FIELDS) + response = self.api.getTestCaseCustomFieldTestPlanDesignValue( + 'cf_full', '7760', 1, '7761', '779') + self.assertEqual('a custom PlanDesign string', response['value']) + self.assertEqual('0', response['enable_on_design']) + self.assertEqual('1', response['enable_on_testplan_design']) + self.assertEqual('0', response['enable_on_execution']) + + def test_getTestSuiteCustomFieldDesignValue_notAssigned(self): + self.api.loadScenario(SCENARIO_CUSTOM_FIELDS) + response = self.api.getTestSuiteCustomFieldDesignValue( + 'cf_notAssigned', '7760', '7762') + self.assertEqual(None, response) + self.assertEqual(self.api.devKey, self.api.callArgs['devKey']) + + def test_getTestSuiteCustomFieldDesignValue_full(self): + self.api.loadScenario(SCENARIO_CUSTOM_FIELDS) + response = self.api.getTestSuiteCustomFieldDesignValue( + 'cf_full', '7760', '7762') + self.assertEqual('a custom TSuite string', response['value']) + self.assertEqual('1', response['enable_on_design']) + self.assertEqual('0', response['enable_on_testplan_design']) + self.assertEqual('0', response['enable_on_execution']) + + def test_getTestPlanCustomFieldDesignValue_notAssigned(self): + self.api.loadScenario(SCENARIO_CUSTOM_FIELDS) + response = self.api.getTestPlanCustomFieldDesignValue( + 'cf_notAssigned', '7760', '7761') + self.assertEqual(None, response) + self.assertEqual(self.api.devKey, self.api.callArgs['devKey']) + + def test_getTestPlanCustomFieldDesignValue_full(self): + self.api.loadScenario(SCENARIO_CUSTOM_FIELDS) + response = self.api.getTestPlanCustomFieldDesignValue( + 'cf_full', '7760', '7761') + self.assertEqual('a custom TPlan string', response['value']) + self.assertEqual('1', response['enable_on_design']) + self.assertEqual('0', response['enable_on_testplan_design']) + self.assertEqual('0', response['enable_on_execution']) + + def test_getReqSpecCustomFieldDesignValue_notAssigned(self): + self.api.loadScenario(SCENARIO_CUSTOM_FIELDS) + response = self.api.getReqSpecCustomFieldDesignValue( + 'cf_notAssigned', '7760', '7789') + self.assertEqual(None, response) + self.assertEqual(self.api.devKey, self.api.callArgs['devKey']) + + def test_getReqSpecCustomFieldDesignValue_full(self): + self.api.loadScenario(SCENARIO_CUSTOM_FIELDS) + response = self.api.getReqSpecCustomFieldDesignValue( + 'cf_full', '7760', '7789') + self.assertEqual('a custom ReqSpec string', response['value']) + self.assertEqual('1', response['enable_on_design']) + self.assertEqual('0', response['enable_on_testplan_design']) + self.assertEqual('0', response['enable_on_execution']) + + def test_getRequirementCustomFieldDesignValue_notAssigned(self): + self.api.loadScenario(SCENARIO_CUSTOM_FIELDS) + response = self.api.getRequirementCustomFieldDesignValue( + 'cf_notAssigned', '7760', '7791') + self.assertEqual(None, response) + self.assertEqual(self.api.devKey, self.api.callArgs['devKey']) + + def test_getRequirementCustomFieldDesignValue_full(self): + self.api.loadScenario(SCENARIO_CUSTOM_FIELDS) + response = self.api.getRequirementCustomFieldDesignValue( + 'cf_full', '7760', '7791') + self.assertEqual('a custom Req string', response['value']) + self.assertEqual('1', response['enable_on_design']) + self.assertEqual('0', response['enable_on_testplan_design']) + self.assertEqual('0', response['enable_on_execution']) + + def test_reportTCResult_user(self): + self.api.loadScenario(SCENARIO_A) + response = self.api.reportTCResult(4712, 'p', testcaseid=4711, + buildname='build 4713', notes='note 4714', + user='a login name') + self.assertEqual('reportTCResult', response[0]['operation']) + self.assertEqual(self.api.devKey, self.api.callArgs['devKey']) + self.assertEqual('a login name', self.api.callArgs['user']) + + def test__getAttachmentArgs_textfile(self): + "py3 issue #39 TypeError: expected bytes-like object, not str" + # under py2, on windows text files should be open with 'r' mode and + # binary files with 'rb' + # see http://docs.python.org/2/tutorial/inputoutput.html#reading-and-writing-files + # under py3, text files open with 'r' on windows makes problem + # see https://github.com/lczub/TestLink-API-Python-client/issues/39 + a_file=open(ATTACHMENT_EXAMPLE_TEXT) + args = self.api._getAttachmentArgs(a_file) + # repeating this test failed in second run, cause filename is then + # 'testlinkapigeneric_offline_test.pyc' + self.assertEqual('testlinkapigeneric_offline_test.py', args['filename']) + # filetype is also OS depended, either 'text/plain' or 'text/x-python' + self.assertIn('text/', args['filetype']) + self.assertIsNotNone(args['content']) + + def test__getAttachmentArgs_filepath(self): + "enhancement #40 handle file patch instead file object" + args = self.api._getAttachmentArgs(ATTACHMENT_EXAMPLE_TEXT) + self.assertEqual('testlinkapigeneric_offline_test.py', args['filename']) + # filetype is also OS depended, either 'text/plain' or 'text/x-python' + self.assertIn('text/', args['filetype']) + self.assertIsNotNone(args['content']) + + def test___str__pyversion(self): + self.api.loadScenario(SCENARIO_TL199) + api_info = self.api.__str__() + py_info = '(PY %i.' % sys.version_info[0] + self.assertIn(py_info, api_info) + + def test_getProjectKeywords_noKeywords(self): + self.api.loadScenario(SCENARIO_A) + response = self.api.getProjectKeywords('noKeyword') + self.assertEqual({}, response) + self.assertEqual(self.api.devKey, self.api.callArgs['devKey']) + + def test_getProjectKeywords_twoKeywords(self): + self.api.loadScenario(SCENARIO_A) + response = self.api.getProjectKeywords('twoKeywords') + self.assertEqual('KeyWord01', response['25']) + self.assertEqual('KeyWord02', response['26']) + + def test_getTestCaseKeywords_noKeywords(self): + self.api.loadScenario(SCENARIO_A) + response = self.api.getTestCaseKeywords(testcaseid='noKeyword') + self.assertEqual({}, response) + self.assertEqual(self.api.devKey, self.api.callArgs['devKey']) + + def test_getProjectKeywords_twoKeywords(self): + self.api.loadScenario(SCENARIO_A) + response = self.api.getTestCaseKeywords(testcaseid='twoKeywords') + self.assertEqual('KeyWord01', response['25']) + self.assertEqual('KeyWord02', response['26']) + + def test_getProjects_noProject(self): + self.api.loadScenario(SCENARIO_NO_PROJECT) + response = self.api.getProjects() + self.assertEqual([], response) + self.assertEqual(self.api.devKey, self.api.callArgs['devKey']) + + def test_connect_with_proxy(self): + """ create a TestLink Generic API dummy with ProxiedTransport""" + self.api = DummyAPIGeneric('http://SERVER-URL-71', 'DEVKEY-71', + transport='PROXY-71') + self.assertEqual('PROXY-71', self.api.server.__call__('transport')) + + def test_getRequirements_allRequirements(self): + self.api.loadScenario(SCENARIO_REQUIREMENTS) + response = self.api.getRequirements('allReqs') + self.assertEqual('feature-01', response[5]['req_doc_id']) + self.assertEqual(self.api.devKey, self.api.callArgs['devKey']) + + def test_getRequirements_noRequirements(self): + self.api.loadScenario(SCENARIO_REQUIREMENTS) + response = self.api.getRequirements('noReqs') + self.assertEqual([], response) + self.assertEqual(self.api.devKey, self.api.callArgs['devKey']) + + def Coveredtest_getReqCoverage_Covered(self): + self.api.loadScenario(SCENARIO_REQUIREMENTS) + response = self.api.getReqCoverage('reqCovered', 'a_req_doc_id') + self.assertEqual('TESTCASE_B', response[0]['name']) + self.assertEqual('15326', response[0]['id']) + self.assertEqual(self.api.devKey, self.api.callArgs['devKey']) + + def test_getReqCoverage_notCovered(self): + self.api.loadScenario(SCENARIO_REQUIREMENTS) + response = self.api.getReqCoverage('reqNotCovered', 'a_req_doc_id') + self.assertEqual([], response) + self.assertEqual(self.api.devKey, self.api.callArgs['devKey']) + + def test_connect_with_use_datetime(self): + """ create a TestLink Generic API dummy with use_datetime""" + self.api = DummyAPIGeneric('http://SERVER-URL-71', 'DEVKEY-71', + use_datetime='datetime?') + a_transport = self.api.server.__call__('transport') + self.assertEqual('datetime?', a_transport._use_datetime) + + def test_connect_with_context(self): + """ create a TestLink Generic API dummy with use_datetime""" + self.api = DummyAPIGeneric('https://SERVER-URL-71', 'DEVKEY-71', + context='ssl_context') + a_transport = self.api.server.__call__('transport') + self.assertEqual('ssl_context', a_transport.context) + +if __name__ == "__main__": + #import sys;sys.argv = ['', 'Test.testName'] unittest.main() \ No newline at end of file diff --git a/test/utest-offline/testlinkargs_test.py b/test/utest-offline/testlinkargs_test.py index 4ea629e..38304b9 100644 --- a/test/utest-offline/testlinkargs_test.py +++ b/test/utest-offline/testlinkargs_test.py @@ -1,7 +1,7 @@ #! /usr/bin/python # -*- coding: UTF-8 -*- -# Copyright 2013-2015 Luiko Czub, TestLink-API-Python-client developers +# Copyright 2013-2019 Luiko Czub, TestLink-API-Python-client developers # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -20,132 +20,103 @@ # this test works WITHOUT an online TestLink Server # no calls are send to a TestLink Server -import sys - -if sys.version_info[0] == 2 and sys.version_info[1] == 6: - # py26 needs backport unittest2 - import unittest2 as unittest -else: - import unittest - -if sys.version_info[0] == 2 and sys.version_info[1] == 7: - # py27 and py31 assertRaisesRegexp was renamed in py32 to assertRaisesRegex - unittest.TestCase.assertRaisesRegex = unittest.TestCase.assertRaisesRegexp - +import pytest # from testlink.testlinkapigeneric import testlinkargs from testlink import testlinkargs - -class testlinkargsTestCase(unittest.TestCase): - """ TestCases for module testlinkargs """ - - def setUp(self): - """ backup TestLinkHelper related environment variables """ - - # module under test - self.mut = testlinkargs - # reset the args cache - self.mut._resetRegister() - # api simulation - self.api = self - - def tearDown(self): - # reset the args cache - self.mut._resetRegister() - - def test__resetRegister(self): - self.mut._apiMethodsArgs['BigBird'] = 'not a Small Bird' - self.assertIsNotNone(self.mut._apiMethodsArgs.get('BigBird')) - self.mut._resetRegister() - self.assertIsNone(self.mut._apiMethodsArgs.get('BigBird')) - - def test_registerMethod(self): - self.mut.registerMethod('DummyMethod', ['Uno', 'due', 'tre'], - ['quad','tre'], ['cinque']) - a_def = self.mut._apiMethodsArgs['DummyMethod'] - self.assertEqual((['Uno', 'due', 'tre'], ['Uno', 'due', 'tre', 'quad'], - ['cinque']), a_def ) - - def test_registerMethod_noArgs(self): - self.mut.registerMethod('DummyMethod') - a_def = self.mut._apiMethodsArgs['DummyMethod'] - self.assertEqual(([], [], []), a_def ) - - def test_registerMethod_onlyArgsOptional(self): - self.mut.registerMethod('DummyMethod', apiArgsOptional=['quad','tre']) - a_def = self.mut._apiMethodsArgs['DummyMethod'] - self.assertEqual(([], ['quad','tre'], []), a_def ) - - def test_registerMethod_onlyArgsPositional(self): - self.mut.registerMethod('DummyMethod', ['Uno', 'due', 'tre']) - a_def = self.mut._apiMethodsArgs['DummyMethod'] - self.assertEqual((['Uno', 'due', 'tre'], ['Uno', 'due', 'tre'], []), - a_def ) - - def test_getMethodsWithPositionalArgs(self): - self.mut.registerMethod('Method_3pos_0opt', ['Uno', 'due', 'tre']) - self.mut.registerMethod('Method_0pos_2opt', [], ['Uno', 'due']) - self.mut.registerMethod('Method_1pos_2opt', ['Uno'], ['due', 'tre']) - a_def = self.mut.getMethodsWithPositionalArgs() - self.assertEqual({'Method_3pos_0opt' : ['Uno', 'due', 'tre'], - 'Method_1pos_2opt' : ['Uno']}, - a_def ) - - def test_registerMethod_ErrorAlreadyDefined(self): - self.mut.registerMethod('DummyMethod', ['Uno', 'due', 'tre'], - ['quad','tre'], ['cinque']) - with self.assertRaisesRegex(testlinkargs.TLArgError, - 'DummyMethod already registered'): - self.mut.registerMethod('DummyMethod') - - def test_registerArgOptional(self): - self.mut.registerMethod('DummyMethod', ['Uno', 'due', 'tre'], - ['quad','tre'], ['cinque']) - self.mut.registerArgOptional('DummyMethod', 'sei') - a_def = self.mut._apiMethodsArgs['DummyMethod'] - self.assertEqual((['Uno', 'due', 'tre'], - ['Uno', 'due', 'tre', 'quad', 'sei'], - ['cinque']), a_def ) - - def test_registerArgOptional_ErrorUnknownMethod(self): - with self.assertRaisesRegex(testlinkargs.TLArgError, - 'DummyMethod not registered'): - self.mut.registerArgOptional('DummyMethod', 'sei') - - def test_registerArgNonApi(self): - self.mut.registerMethod('DummyMethod', ['Uno', 'due', 'tre'], - ['quad','tre'], ['cinque']) - self.mut.registerArgNonApi('DummyMethod', 'sei') - a_def = self.mut._apiMethodsArgs['DummyMethod'] - self.assertEqual((['Uno', 'due', 'tre'], - ['Uno', 'due', 'tre', 'quad'], - ['cinque', 'sei']), a_def ) - - def test_getArgsForMethod_onlyOptionalArgs(self): - self.mut.registerMethod('DummyMethod', ['Uno', 'due', 'tre'], - ['quad','tre']) - response = self.mut.getArgsForMethod('DummyMethod') - self.assertEqual(response, (['Uno', 'due', 'tre', 'quad'], []) ) - - def test_getArgsForMethod_OptionalAndPositionalArgs(self): - self.mut.registerMethod('DummyMethod', ['Uno', 'due', 'tre'], - ['quad','tre']) - response = self.mut.getArgsForMethod('DummyMethod', ['Uno', 'quad']) - self.assertEqual(response, (['due', 'tre'], []) ) - - def test_getArgsForMethod_nonApiArgs(self): - self.mut.registerMethod('DummyMethod', ['Uno', 'due', 'tre'], - ['quad','tre'], ['cinque']) - response = self.mut.getArgsForMethod('DummyMethod', - ['Uno', 'due', 'tre']) - self.assertEqual(response, (['quad'], ['cinque']) ) - - def test_getArgsForMethod_unknownMethods(self): - with self.assertRaisesRegex(testlinkargs.TLArgError, - 'unknownMethod not registered'): - self.mut.getArgsForMethod('unknownMethod') - - -if __name__ == "__main__": - #import sys;sys.argv = ['', 'Test.testName'] - unittest.main() \ No newline at end of file +@pytest.fixture() +def args_register(): + """ reset singelton testlinkargs before and after the test """ + testlinkargs._resetRegister() + yield testlinkargs + testlinkargs._resetRegister() + +def test__resetRegister(args_register): + args_register._apiMethodsArgs['BigBird'] = 'not a Small Bird' + assert None != args_register._apiMethodsArgs.get('BigBird') + args_register._resetRegister() + assert None == args_register._apiMethodsArgs.get('BigBird') + + +def test_registerMethod(args_register): + args_register.registerMethod('DummyMethod', ['Uno', 'due', 'tre'], + ['quad','tre'], ['cinque']) + a_def = args_register._apiMethodsArgs['DummyMethod'] + assert a_def == (['Uno', 'due', 'tre'], ['Uno', 'due', 'tre', 'quad'], + ['cinque']) + +def test_registerMethod_noArgs(args_register): + args_register.registerMethod('DummyMethod') + a_def = args_register._apiMethodsArgs['DummyMethod'] + assert a_def == ([], [], []) + +def test_registerMethod_onlyArgsOptional(args_register): + args_register.registerMethod('DummyMethod', apiArgsOptional=['quad','tre']) + a_def = args_register._apiMethodsArgs['DummyMethod'] + assert a_def == ([], ['quad','tre'], []) + +def test_registerMethod_onlyArgsPositional(args_register): + args_register.registerMethod('DummyMethod', ['Uno', 'due', 'tre']) + a_def = args_register._apiMethodsArgs['DummyMethod'] + assert a_def == (['Uno', 'due', 'tre'], ['Uno', 'due', 'tre'], []) + +def test_getMethodsWithPositionalArgs(args_register): + args_register.registerMethod('Method_3pos_0opt', ['Uno', 'due', 'tre']) + args_register.registerMethod('Method_0pos_2opt', [], ['Uno', 'due']) + args_register.registerMethod('Method_1pos_2opt', ['Uno'], ['due', 'tre']) + a_def = args_register.getMethodsWithPositionalArgs() + assert a_def == {'Method_3pos_0opt' : ['Uno', 'due', 'tre'], + 'Method_1pos_2opt' : ['Uno']} + +def test_registerMethod_ErrorAlreadyDefined(args_register): + args_register.registerMethod('DummyMethod', ['Uno', 'due', 'tre'], + ['quad','tre'], ['cinque']) + with pytest.raises(testlinkargs.TLArgError, + match='DummyMethod already registered'): + args_register.registerMethod('DummyMethod') + +def test_registerArgOptional(args_register): + args_register.registerMethod('DummyMethod', ['Uno', 'due', 'tre'], + ['quad','tre'], ['cinque']) + args_register.registerArgOptional('DummyMethod', 'sei') + a_def = args_register._apiMethodsArgs['DummyMethod'] + assert a_def == (['Uno', 'due', 'tre'], + ['Uno', 'due', 'tre', 'quad', 'sei'], ['cinque']) + +def test_registerArgOptional_ErrorUnknownMethod(args_register): + with pytest.raises(testlinkargs.TLArgError, + match='DummyMethod not registered'): + args_register.registerArgOptional('DummyMethod', 'sei') + +def test_registerArgNonApi(args_register): + args_register.registerMethod('DummyMethod', ['Uno', 'due', 'tre'], + ['quad','tre'], ['cinque']) + args_register.registerArgNonApi('DummyMethod', 'sei') + a_def = args_register._apiMethodsArgs['DummyMethod'] + assert a_def == (['Uno', 'due', 'tre'], ['Uno', 'due', 'tre', 'quad'], + ['cinque', 'sei']) + +def test_getArgsForMethod_onlyOptionalArgs(args_register): + args_register.registerMethod('DummyMethod', ['Uno', 'due', 'tre'], + ['quad','tre']) + response = args_register.getArgsForMethod('DummyMethod') + assert response == (['Uno', 'due', 'tre', 'quad'], []) + +def test_getArgsForMethod_OptionalAndPositionalArgs(args_register): + args_register.registerMethod('DummyMethod', ['Uno', 'due', 'tre'], + ['quad','tre']) + response = args_register.getArgsForMethod('DummyMethod', ['Uno', 'quad']) + assert response == (['due', 'tre'], []) + +def test_getArgsForMethod_nonApiArgs(args_register): + args_register.registerMethod('DummyMethod', ['Uno', 'due', 'tre'], + ['quad','tre'], ['cinque']) + response = args_register.getArgsForMethod('DummyMethod', + ['Uno', 'due', 'tre']) + assert response == (['quad'], ['cinque']) + +def test_getArgsForMethod_unknownMethods(args_register): + with pytest.raises(testlinkargs.TLArgError, + match='unknownMethod not registered'): + args_register.getArgsForMethod('unknownMethod') + \ No newline at end of file diff --git a/test/utest-offline/testlinkdecorators_test.py b/test/utest-offline/testlinkdecorators_test.py index 6a88ce1..59b35d3 100644 --- a/test/utest-offline/testlinkdecorators_test.py +++ b/test/utest-offline/testlinkdecorators_test.py @@ -1,7 +1,7 @@ #! /usr/bin/python # -*- coding: UTF-8 -*- -# Copyright 2013-2015 Luiko Czub, TestLink-API-Python-client developers +# Copyright 2013-2019 Luiko Czub, TestLink-API-Python-client developers # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -19,18 +19,11 @@ # this test works WITHOUT an online TestLink Server # no calls are send to a TestLink Server +# +# TestCases for decorators, used by TestlinkAPIGeneric building +# TestLink API methods. -import sys - -if sys.version_info[0] == 2 and sys.version_info[1] == 6: - # py26 needs backport unittest2 - import unittest2 as unittest -else: - import unittest - -if sys.version_info[0] == 2 and sys.version_info[1] == 7: - # py27 and py31 assertRaisesRegexp was renamed in py32 to assertRaisesRegex - unittest.TestCase.assertRaisesRegex = unittest.TestCase.assertRaisesRegexp +import pytest from testlink.testlinkerrors import TLResponseError from testlink.testlinkargs import registerMethod, getArgsForMethod @@ -40,16 +33,12 @@ decoMakerApiCallChangePosToOptArg - - -class testlinkdecoratorsTestCase(unittest.TestCase): - """ TestCases for decorators, used by TestlinkAPIGeneric for build - TestLink API methodsServer. """ - +class dummy_api_testlinkdecorator(object): + """ class simulating testlink api client with required attributes for + testlinkdecorators_test """ + devKey = '007' - def setUp(self): - self.api = self - + def _getAttachmentArgs(self, attachmentfile): # simulation of TestlinkAPIGeneric._getAttachmentArgs() # needed in test_decoApiCallAddAttachment @@ -57,243 +46,245 @@ def _getAttachmentArgs(self, attachmentfile): 'filetype': 'type %s' % attachmentfile, 'content' : 'content %s' % attachmentfile} -# def tearDown(self): -# pass - - def test_noWrapperName_decoApiCallWithoutArgs(self): - " decorator test: original function name should be unchanged " - - @decoApiCallWithoutArgs - def orig_funcname1(a_api): - "orig doc string1" - return 'noArgs' - - self.assertEqual('orig_funcname1', orig_funcname1.__name__) - self.assertEqual('orig doc string1', orig_funcname1.__doc__) - self.assertIn('testlinkdecorators_test', orig_funcname1.__module__) - - def test_decoApiCallWithArgs(self): - " decorator test: positional and optional arguments should be registered " - - from testlink.testlinkargs import getMethodsWithPositionalArgs - @decoMakerApiCallWithArgs(['Uno', 'due', 'tre'], ['quad']) - def DummyMethod(a_api): - "a dummy api method with 3 positional args and 1 optional arg" - pass - - posArgs = getMethodsWithPositionalArgs() - self.assertEqual(['Uno', 'due', 'tre'], posArgs['DummyMethod']) - - def test_noWrapperName_decoApiCallWithArgs(self): - " decorator test: original function name should be unchanged " - - @decoMakerApiCallWithArgs() - def orig_funcname2(a_api): - "orig doc string2" - return 'noArgs' - - self.assertEqual('orig_funcname2', orig_funcname2.__name__) - self.assertEqual('orig doc string2', orig_funcname2.__doc__) - self.assertIn('testlinkdecorators_test', orig_funcname2.__module__) - - def test_decoApiCallAddDevKey(self): - " decorator test: argsOptional should be extended with devKey" - - registerMethod('a_func') - @decoApiCallAddDevKey - def a_func(a_api, *argsPositional, **argsOptional): - return argsPositional, argsOptional - - # check method argument definition - allArgs = getArgsForMethod('a_func') - self.assertEqual((['devKey'], []), allArgs) - # check call arguments - response = a_func(self.api) - self.assertEqual({'devKey' : self.api.devKey}, response[1]) - - def test_noWrapperName_decoApiCallAddDevKey(self): - " decorator test: original function name should be unchanged " - - registerMethod('orig_funcname3') - @decoApiCallAddDevKey - def orig_funcname3(a_api, *argsPositional, **argsOptional): - "orig doc string3" - return argsPositional, argsOptional - - self.assertEqual('orig_funcname3', orig_funcname3.__name__) - self.assertEqual('orig doc string3', orig_funcname3.__doc__) - self.assertIn('testlinkdecorators_test', orig_funcname3.__module__) - - def test_decoApiCallReplaceTLResponseError_NoCodeError(self): - " decorator test: TLResponseError (code=None) should be handled " - - @decoMakerApiCallReplaceTLResponseError() - def a_func(a_api, *argsPositional, **argsOptional): - raise TLResponseError('DummyMethod', - argsOptional, 'Empty Response! ') - - response = a_func(self.api) - self.assertEqual([], response) - - def test_decoApiCallReplaceTLResponseError_CodeError(self): - " decorator test: TLResponseError (code=777) should be raised " - - @decoMakerApiCallReplaceTLResponseError() - def a_func(a_api, *argsPositional, **argsOptional): - raise TLResponseError('DummyMethod', - argsOptional, 'Empty Response! ', 777) - - with self.assertRaisesRegex(TLResponseError, '777.*Empty'): - a_func(self.api) - - def test_decoApiCallReplaceTLResponseError_CodeErrorOk(self): - " decorator test: TLResponseError (code=777) should be handled " - - @decoMakerApiCallReplaceTLResponseError(777) - def a_func(a_api, *argsPositional, **argsOptional): - raise TLResponseError('DummyMethod', - argsOptional, 'Empty Response! ', 777) - - response = a_func(self.api) - self.assertEqual([], response) - - def test_decoApiCallReplaceTLResponseError_NoError(self): - " decorator test: response without TLResponseError should be passed " - - @decoMakerApiCallReplaceTLResponseError(777) - def a_func(a_api, *argsPositional, **argsOptional): - return argsOptional - - response = a_func(self.api, name='BigBird') - self.assertEqual({'name' : 'BigBird'}, response) - - def test_decoApiCallReplaceTLResponseError_replaceValue(self): - " decorator test: TLResponseError should be replaced with {}" - - @decoMakerApiCallReplaceTLResponseError(replaceValue={}) - def a_func(a_api, *argsPositional, **argsOptional): - raise TLResponseError('DummyMethod', - argsOptional, 'Empty Response! ') - - response = a_func(self.api) - self.assertEqual({}, response) - - def test_noWrapperName_decoApiCallReplaceTLResponseError(self): - " decorator test: original function name should be unchanged " - - @decoMakerApiCallReplaceTLResponseError() - def orig_funcname4(a_api, *argsPositional, **argsOptional): - "orig doc string4" - return argsPositional, argsOptional - - self.assertEqual('orig_funcname4', orig_funcname4.__name__) - self.assertEqual('orig doc string4', orig_funcname4.__doc__) - self.assertIn('testlinkdecorators_test', orig_funcname4.__module__) - - def test_decoApiCallAddAttachment(self): - " decorator test: argsOptional should be extended attachment file infos" - - registerMethod('func_addAttachment') - @decoApiCallAddAttachment - def func_addAttachment(a_api, *argsPositional, **argsOptional): - return argsPositional, argsOptional - - # check method argument definition - allArgs = getArgsForMethod('func_addAttachment') - self.assertEqual((['devKey'], ['attachmentfile']), allArgs) - # check call arguments - response = func_addAttachment(self.api, 'a_file') - self.assertEqual({'devKey' : self.api.devKey, 'filename': 'name a_file', - 'filetype': 'type a_file', 'content' : 'content a_file'}, - response[1]) - - def test_noWrapperName_decoApiCallAddAttachment(self): - " decorator test: original function name should be unchanged " - - registerMethod('orig_funcname5') - @decoApiCallAddAttachment - def orig_funcname5(a_api): - "orig doc string5" - return 'noArgs' - - self.assertEqual('orig_funcname5', orig_funcname5.__name__) - self.assertEqual('orig doc string5', orig_funcname5.__doc__) - self.assertIn('testlinkdecorators_test', orig_funcname5.__module__) - - def test_noWrapperName_decoApiCallChangePosToOptArg(self): - " decorator test: original function name should be unchanged " - - @decoMakerApiCallChangePosToOptArg(2, 'optArgName') - def orig_funcname6(*argsPositional, **argsOptional): - "orig doc string6" - return argsPositional, argsOptional - - self.assertEqual('orig_funcname6', orig_funcname6.__name__) - self.assertEqual('orig doc string6', orig_funcname6.__doc__) - self.assertIn('testlinkdecorators_test', orig_funcname6.__module__) - - def test_decoApiCallChangePosToOptArg_posArg2(self): - " decorator test: change posArg 2" - - @decoMakerApiCallChangePosToOptArg(2, 'due') - def a_func(a_api, *argsPositional, **argsOptional): - return argsPositional, argsOptional - - #'Uno', 'due', 'tre', 'quad', 'cinque' - # 2 posArgs 2optArgs -> 1posArg, 3optArg - (posArgs, optArgs) = a_func(self.api, 1, 2, tre = 3, quad = 4 ) - self.assertEqual((1,), posArgs) - self.assertEqual({'due' : 2, 'tre' : 3, 'quad' : 4 }, optArgs) - - # 3 posArgs 2optArgs -> 2posArg, 2optArg - (posArgs, optArgs) = a_func(self.api, 1, 2, 3, quad = 4 , due = 5) - self.assertEqual((1,3), posArgs) - self.assertEqual({'due' : 2, 'quad' : 4 }, optArgs) - - # 1 posArgs 2optArgs -> 1posArg, 2optArg - (posArgs, optArgs) = a_func(self.api, 1, due = 2, tre = 3) - self.assertEqual((1,), posArgs) - self.assertEqual({'due' : 2, 'tre' : 3 }, optArgs) - - # 0 posArgs 2optArgs -> 0posArg, 2optArg - (posArgs, optArgs) = a_func(self.api, uno = 1, due = 2) - self.assertEqual( (), posArgs) - self.assertEqual({'uno' : 1, 'due' :2}, optArgs) - - def test_decoApiCallChangePosToOptArg_posArg3(self): - " decorator test: change posArg 3" - - @decoMakerApiCallChangePosToOptArg(3, 'tre') - def a_func(a_api, *argsPositional, **argsOptional): - return argsPositional, argsOptional - - # 3 posArgs 0optArgs -> 2posArg, 1optArg - (posArgs, optArgs) = a_func(self.api, 1, 2, 3 ) - self.assertEqual((1,2), posArgs) - self.assertEqual({'tre' : 3}, optArgs) - - # 2 posArgs 0optArgs -> 2posArg, 0optArg - (posArgs, optArgs) = a_func(self.api, 1, 2 ) - self.assertEqual((1,2), posArgs) - self.assertEqual({}, optArgs) - - def test_decoApiCallChangePosToOptArg_posArgNeg1(self): - " decorator test: change posArg -1" - - @decoMakerApiCallChangePosToOptArg(-1, 'last') - def a_func(a_api, *argsPositional, **argsOptional): - return argsPositional, argsOptional - - # 3 posArgs 0optArgs -> 2posArg, 1optArg - (posArgs, optArgs) = a_func(self.api, 1, 2, 3 ) - self.assertEqual((1,2,3), posArgs) - self.assertEqual({}, optArgs) - - # 1 posArgs 0optArgs -> 0posArg, 1optArg - (posArgs, optArgs) = a_func(self.api, 1 ) - self.assertEqual((1,), posArgs) - self.assertEqual({}, optArgs) - -if __name__ == "__main__": - #import sys;sys.argv = ['', 'Test.testName'] - unittest.main() \ No newline at end of file +@pytest.fixture() +def dummy_api(): + """ simulates testlink api client with required attributes devKey and + _getAttachmentArgs + """ + return dummy_api_testlinkdecorator() + +def test_noWrapperName_decoApiCallWithoutArgs(): + " decorator test: original function name should be unchanged " + + @decoApiCallWithoutArgs + def orig_funcname1(a_api): + "orig doc string1" + return 'noArgs' + + assert 'orig_funcname1' == orig_funcname1.__name__ + assert 'orig doc string1' == orig_funcname1.__doc__ + assert 'testlinkdecorators_test' in orig_funcname1.__module__ + +def test_decoApiCallWithArgs(): + " decorator test: positional and optional arguments should be registered " + + from testlink.testlinkargs import getMethodsWithPositionalArgs + @decoMakerApiCallWithArgs(['Uno', 'due', 'tre'], ['quad']) + def DummyMethod(a_api): + "a dummy api method with 3 positional args and 1 optional arg" + pass + + posArgs = getMethodsWithPositionalArgs() + assert ['Uno', 'due', 'tre'] == posArgs['DummyMethod'] + +def test_noWrapperName_decoApiCallWithArgs(): + " decorator test: original function name should be unchanged " + + @decoMakerApiCallWithArgs() + def orig_funcname2(a_api): + "orig doc string2" + return 'noArgs' + + assert 'orig_funcname2' == orig_funcname2.__name__ + assert 'orig doc string2' == orig_funcname2.__doc__ + assert 'testlinkdecorators_test' in orig_funcname2.__module__ + +def test_decoApiCallAddDevKey(dummy_api): + " decorator test: argsOptional should be extended with devKey" + + registerMethod('a_func') + @decoApiCallAddDevKey + def a_func(a_api, *argsPositional, **argsOptional): + return argsPositional, argsOptional + + # check method argument definition + allArgs = getArgsForMethod('a_func') + assert (['devKey'], []) == allArgs + # check call arguments + response = a_func(dummy_api) + assert {'devKey' : dummy_api.devKey} == response[1] + +def test_noWrapperName_decoApiCallAddDevKey(): + " decorator test: original function name should be unchanged " + + registerMethod('orig_funcname3') + @decoApiCallAddDevKey + def orig_funcname3(a_api, *argsPositional, **argsOptional): + "orig doc string3" + return argsPositional, argsOptional + + assert 'orig_funcname3' == orig_funcname3.__name__ + assert 'orig doc string3' == orig_funcname3.__doc__ + assert 'testlinkdecorators_test' in orig_funcname3.__module__ + +def test_decoApiCallReplaceTLResponseError_NoCodeError(): + " decorator test: TLResponseError (code=None) should be handled " + + @decoMakerApiCallReplaceTLResponseError() + def a_func(a_api, *argsPositional, **argsOptional): + raise TLResponseError('DummyMethod', + argsOptional, 'Empty Response! ') + + response = a_func('dummy_api') + assert [] == response + +def test_decoApiCallReplaceTLResponseError_CodeError(): + " decorator test: TLResponseError (code=777) should be raised " + + @decoMakerApiCallReplaceTLResponseError() + def a_func(a_api, *argsPositional, **argsOptional): + raise TLResponseError('DummyMethod', + argsOptional, 'Empty Response! ', 777) + + with pytest.raises(TLResponseError, match='777.*Empty'): + a_func('dummy_api') + +def test_decoApiCallReplaceTLResponseError_CodeErrorOk(): + " decorator test: TLResponseError (code=777) should be handled " + + @decoMakerApiCallReplaceTLResponseError(777) + def a_func(a_api, *argsPositional, **argsOptional): + raise TLResponseError('DummyMethod', + argsOptional, 'Empty Response! ', 777) + + response = a_func('dummy_api') + assert [] == response + +def test_decoApiCallReplaceTLResponseError_NoError(): + " decorator test: response without TLResponseError should be passed " + + @decoMakerApiCallReplaceTLResponseError(777) + def a_func(a_api, *argsPositional, **argsOptional): + return argsOptional + + response = a_func('dummy_api', name='BigBird') + assert {'name' : 'BigBird'} == response + +def test_decoApiCallReplaceTLResponseError_replaceValue(): + " decorator test: TLResponseError should be replaced with {}" + + @decoMakerApiCallReplaceTLResponseError(replaceValue={}) + def a_func(a_api, *argsPositional, **argsOptional): + raise TLResponseError('DummyMethod', + argsOptional, 'Empty Response! ') + + response = a_func('dummy_api') + assert {} == response + +def test_noWrapperName_decoApiCallReplaceTLResponseError(): + " decorator test: original function name should be unchanged " + + @decoMakerApiCallReplaceTLResponseError() + def orig_funcname4(a_api, *argsPositional, **argsOptional): + "orig doc string4" + return argsPositional, argsOptional + + assert 'orig_funcname4' == orig_funcname4.__name__ + assert 'orig doc string4' == orig_funcname4.__doc__ + assert 'testlinkdecorators_test' in orig_funcname4.__module__ + +def test_decoApiCallAddAttachment(dummy_api): + " decorator test: argsOptional should be extended attachment file infos" + + registerMethod('func_addAttachment') + @decoApiCallAddAttachment + def func_addAttachment(a_api, *argsPositional, **argsOptional): + return argsPositional, argsOptional + + # check method argument definition + allArgs = getArgsForMethod('func_addAttachment') + assert (['devKey'], ['attachmentfile']) == allArgs + # check call arguments + response = func_addAttachment(dummy_api, 'a_file') + assert response[1] == {'devKey' : dummy_api.devKey, + 'filename': 'name a_file', + 'filetype': 'type a_file', + 'content' : 'content a_file'} + +def test_noWrapperName_decoApiCallAddAttachment(): + " decorator test: original function name should be unchanged " + + registerMethod('orig_funcname5') + @decoApiCallAddAttachment + def orig_funcname5(a_api): + "orig doc string5" + return 'noArgs' + + assert 'orig_funcname5' == orig_funcname5.__name__ + assert 'orig doc string5' == orig_funcname5.__doc__ + assert 'testlinkdecorators_test' in orig_funcname5.__module__ + +def test_noWrapperName_decoApiCallChangePosToOptArg(): + " decorator test: original function name should be unchanged " + + @decoMakerApiCallChangePosToOptArg(2, 'optArgName') + def orig_funcname6(*argsPositional, **argsOptional): + "orig doc string6" + return argsPositional, argsOptional + + assert 'orig_funcname6' == orig_funcname6.__name__ + assert 'orig doc string6' == orig_funcname6.__doc__ + assert 'testlinkdecorators_test' in orig_funcname6.__module__ + +def test_decoApiCallChangePosToOptArg_posArg2(): + " decorator test: change posArg 2" + + @decoMakerApiCallChangePosToOptArg(2, 'due') + def a_func(a_api, *argsPositional, **argsOptional): + return argsPositional, argsOptional + + #'Uno', 'due', 'tre', 'quad', 'cinque' + # 2 posArgs 2optArgs -> 1posArg, 3optArg + (posArgs, optArgs) = a_func('dummy_api', 1, 2, tre = 3, quad = 4 ) + assert (1,) == posArgs + assert {'due' : 2, 'tre' : 3, 'quad' : 4 } == optArgs + + # 3 posArgs 2optArgs -> 2posArg, 2optArg + (posArgs, optArgs) = a_func('dummy_api', 1, 2, 3, quad = 4 , due = 5) + assert (1,3) == posArgs + assert {'due' : 2, 'quad' : 4 } == optArgs + + # 1 posArgs 2optArgs -> 1posArg, 2optArg + (posArgs, optArgs) = a_func('dummy_api', 1, due = 2, tre = 3) + assert (1,) == posArgs + assert {'due' : 2, 'tre' : 3 } == optArgs + + # 0 posArgs 2optArgs -> 0posArg, 2optArg + (posArgs, optArgs) = a_func('dummy_api', uno = 1, due = 2) + assert () == posArgs + assert {'uno' : 1, 'due' :2} == optArgs + +def test_decoApiCallChangePosToOptArg_posArg3(): + " decorator test: change posArg 3" + + @decoMakerApiCallChangePosToOptArg(3, 'tre') + def a_func(a_api, *argsPositional, **argsOptional): + return argsPositional, argsOptional + + # 3 posArgs 0optArgs -> 2posArg, 1optArg + (posArgs, optArgs) = a_func('dummy_api', 1, 2, 3 ) + assert (1,2) == posArgs + assert {'tre' : 3} == optArgs + + # 2 posArgs 0optArgs -> 2posArg, 0optArg + (posArgs, optArgs) = a_func('dummy_api', 1, 2 ) + assert (1,2) == posArgs + assert {} == optArgs + +def test_decoApiCallChangePosToOptArg_posArgNeg1(): + " decorator test: change posArg -1" + + @decoMakerApiCallChangePosToOptArg(-1, 'last') + def a_func(a_api, *argsPositional, **argsOptional): + return argsPositional, argsOptional + + # 3 posArgs 0optArgs -> 2posArg, 1optArg + (posArgs, optArgs) = a_func('dummy_api', 1, 2, 3 ) + assert (1,2,3) == posArgs + assert {} == optArgs + + # 1 posArgs 0optArgs -> 0posArg, 1optArg + (posArgs, optArgs) = a_func('dummy_api', 1 ) + assert (1,) == posArgs + assert {} == optArgs + \ No newline at end of file diff --git a/test/utest-offline/testlinkhelper_test.py b/test/utest-offline/testlinkhelper_test.py index f4fe60f..01b99ab 100644 --- a/test/utest-offline/testlinkhelper_test.py +++ b/test/utest-offline/testlinkhelper_test.py @@ -1,7 +1,7 @@ #! /usr/bin/python # -*- coding: UTF-8 -*- -# Copyright 2012-2015 Luiko Czub, TestLink-API-Python-client developers +# Copyright 2012-2019 Luiko Czub, TestLink-API-Python-client developers # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -17,16 +17,12 @@ # # ------------------------------------------------------------------------ +# TestCases for TestLinkHelper # this test works WITHOUT an online TestLink Server # no calls are send to a TestLink Server -import unittest -import os -import sys - - -from testlink import TestLinkHelper - +import pytest, sys +IS_PY3 = sys.version_info[0] > 2 class DummyTestLinkAPI(object): """ Dummy for Simulation TestLinkAPICLient. @@ -38,148 +34,182 @@ def __init__(self, server_url, devKey, **args): self.devKey = devKey self.args = args -class TestLinkHelperTestCase(unittest.TestCase): - """ TestCases for TestLinkHelper """ - - CLASSUNDERTEST = TestLinkHelper - ENVNAMES = ['TESTLINK_API_PYTHON_SERVER_URL', 'TESTLINK_API_PYTHON_DEVKEY', - 'http_proxy'] - EXPECTED_DEFAULTS = ['http://localhost/testlink/lib/api/xmlrpc.php', '42', - ''] - - def setEnviron(self, envname, envvalue ): - """ manipulates os.environ - stores os.environ[envname] = envvalue """ - if envvalue is None: - # UNSET environment variable - if envname in os.environ: - os.environ.pop(envname) - else: - os.environ[envname] = envvalue - - def setUp(self): - """ backup TestLinkHelper related environment variables """ - self.backup = {} - for envname in self.ENVNAMES: - self.backup[envname] = os.getenv(envname) - - def tearDown(self): - """ restore TestLinkHelper related environment variables """ - for envname in self.ENVNAMES: - self.setEnviron(envname, self.backup[envname]) - - def test_init_Env(self): - """ init TestLinkHelper with environment variables """ - self.check_init_env((None, None, None), self.EXPECTED_DEFAULTS) - self.check_init_env(('SERVER-URL-1', None, None), - ('SERVER-URL-1', self.EXPECTED_DEFAULTS[1], - self.EXPECTED_DEFAULTS[2])) - self.check_init_env((None, 'DEVKEY-2', None), - (self.EXPECTED_DEFAULTS[0], 'DEVKEY-2', - self.EXPECTED_DEFAULTS[2])) - self.check_init_env(('SERVER-URL-3', 'DEVKEY-3', None), - ('SERVER-URL-3', 'DEVKEY-3', self.EXPECTED_DEFAULTS[2])) - self.check_init_env((None, None, 'Proxy-4'), - (self.EXPECTED_DEFAULTS[0], self.EXPECTED_DEFAULTS[1], - 'Proxy-4')) - self.check_init_env(('SERVER-URL-5', 'DEVKEY-5', 'Proxy-5'), - ('SERVER-URL-5', 'DEVKEY-5', 'Proxy-5')) - - def check_init_env(self, env_values, expectations ): - # set TestLinkHelper related environment variables - self.setEnviron(self.ENVNAMES[0], env_values[0]) - self.setEnviron(self.ENVNAMES[1], env_values[1]) - self.setEnviron(self.ENVNAMES[2], env_values[2]) - # init helper without method params - a_helper = self.CLASSUNDERTEST() - self.assertEqual(expectations[0], a_helper._server_url) - self.assertEqual(expectations[1], a_helper._devkey) - self.assertEqual(expectations[2], a_helper._proxy) - - def test_init_params(self): - """ init TestLinkHelper with method parameter and no env variables """ - self.check_init_params(('SERVER-URL-11', None, None), - ('SERVER-URL-11', self.EXPECTED_DEFAULTS[1], - self.EXPECTED_DEFAULTS[2])) - self.check_init_params((None, 'DEVKEY-12', None), - (self.EXPECTED_DEFAULTS[0], 'DEVKEY-12', - self.EXPECTED_DEFAULTS[2])) - self.check_init_params(('SERVER-URL-13', 'DEVKEY-13', None), - ('SERVER-URL-13', 'DEVKEY-13', self.EXPECTED_DEFAULTS[2])) - self.check_init_params((None, None, 'Proxy-14'), - (self.EXPECTED_DEFAULTS[0], self.EXPECTED_DEFAULTS[1], - 'Proxy-14')) - self.check_init_params(('SERVER-URL-15', 'DEVKEY-15', 'Proxy-15'), - ('SERVER-URL-15', 'DEVKEY-15', 'Proxy-15')) - - def check_init_params(self, param_values, expectations ): - # unset TestLinkHelper related environment variables - self.setEnviron(self.ENVNAMES[0], None) - self.setEnviron(self.ENVNAMES[1], None) - self.setEnviron(self.ENVNAMES[2], None) - # init helper with method params - a_helper = self.CLASSUNDERTEST(param_values[0], param_values[1], param_values[2]) - self.assertEqual(expectations[0], a_helper._server_url) - self.assertEqual(expectations[1], a_helper._devkey) - self.assertEqual(expectations[2], a_helper._proxy) - - def test_init_env_params(self): - """ init TestLinkHelper with mixed method parameter and env variables """ - # set TestLinkHelper related environment variables - self.setEnviron(self.ENVNAMES[0], 'SERVER-URL-21') - self.setEnviron(self.ENVNAMES[1], 'DEVKEY-21') - self.setEnviron(self.ENVNAMES[2], 'PROXY-21') - # init helper with method params - a_helper = self.CLASSUNDERTEST('SERVER-URL-22', 'DEVKEY-22', 'PROXY-22') - # the method params have a high priority than the environment variables - self.assertEqual('SERVER-URL-22', a_helper._server_url) - self.assertEqual('DEVKEY-22', a_helper._devkey) - self.assertEqual('PROXY-22', a_helper._proxy) - +ENVNAMES = ('TESTLINK_API_PYTHON_SERVER_URL', 'TESTLINK_API_PYTHON_DEVKEY', + 'http_proxy') +EXPECTED_DEFAULTS = ('http://localhost/testlink/lib/api/xmlrpc.php', '42', + '') - def test_createArgparser(self): - """ create TestLinkHelper command line argument parser """ - a_helper = self.CLASSUNDERTEST('SERVER-URL-31', 'DEVKEY-31', 'PROXY-31') - a_parser = a_helper._createArgparser('DESCRIPTION-31') - self.assertEqual('DESCRIPTION-31', a_parser.description) - default_args=a_parser.parse_args('') - self.assertEqual('SERVER-URL-31', default_args.server_url) - self.assertEqual('DEVKEY-31', default_args.devKey) - self.assertEqual('PROXY-31', default_args.proxy) - - def test_setParamsFromArgs(self): - """ set TestLinkHelper params from command line arguments """ - a_helper = self.CLASSUNDERTEST() - a_helper.setParamsFromArgs(None, ['--server_url', 'SERVER-URL-41', - '--devKey' , 'DEVKEY-41']) - self.assertEqual('SERVER-URL-41', a_helper._server_url) - self.assertEqual('DEVKEY-41', a_helper._devkey) - - def test_connect(self): - """ create a TestLink API dummy """ - a_helper = self.CLASSUNDERTEST('SERVER-URL-51', 'DEVKEY-51') - a_tl_api = a_helper.connect(DummyTestLinkAPI) - self.assertEqual('SERVER-URL-51', a_tl_api.server) - self.assertEqual('DEVKEY-51', a_tl_api.devKey) - self.assertEqual({}, a_tl_api.args) - - def test_getProxiedTransport(self): - """ create a ProxiedTransportTestLink API dummy """ - a_helper = self.CLASSUNDERTEST('SERVER-URL-61', 'DEVKEY-61', 'PROXY-61') - #'http://fast.proxy.com.de/') - a_pt = a_helper._getProxiedTransport() - self.assertEqual('ProxiedTransport', a_pt.__class__.__name__) - self.assertEqual('PROXY-61', a_pt.proxy) +def setEnviron(monkeypatch, envname, envvalue ): + """ manipulates environment variable ENVNAME to emulate setting ENVVALUE. + tests must use pytest fixture monkeypatch """ + if envvalue is None: + monkeypatch.delenv(envname, False) + else: + monkeypatch.setenv(envname, envvalue) + + +test_data_init_envs = [ + ((None, None, None), + EXPECTED_DEFAULTS ), + (('SERVER-URL-1', None, None), + ('SERVER-URL-1', EXPECTED_DEFAULTS[1], EXPECTED_DEFAULTS[2])), + ((None, 'DEVKEY-2', None), + (EXPECTED_DEFAULTS[0], 'DEVKEY-2', EXPECTED_DEFAULTS[2])), + (('SERVER-URL-3', 'DEVKEY-3', None), + ('SERVER-URL-3', 'DEVKEY-3', EXPECTED_DEFAULTS[2])), + ((None, None, 'Proxy-4'), + (EXPECTED_DEFAULTS[0], EXPECTED_DEFAULTS[1], 'Proxy-4')), + (('SERVER-URL-5', 'DEVKEY-5', 'Proxy-5'), + ('SERVER-URL-5', 'DEVKEY-5', 'Proxy-5')) + ] + +@pytest.mark.parametrize("env_values, expectations", test_data_init_envs) +def test_init_env(api_helper_class, monkeypatch, env_values, expectations ): + """ init TestLinkHelper with environment variables """ + # set TestLinkHelper related environment variables + setEnviron(monkeypatch, ENVNAMES[0], env_values[0]) + setEnviron(monkeypatch, ENVNAMES[1], env_values[1]) + setEnviron(monkeypatch, ENVNAMES[2], env_values[2]) + # init helper without params + a_helper = api_helper_class() + assert expectations == (a_helper._server_url, a_helper._devkey, + a_helper._proxy) + + +test_data_init_params = [ + (('SERVER-URL-11', None, None), + ('SERVER-URL-11', EXPECTED_DEFAULTS[1], EXPECTED_DEFAULTS[2])), + ((None, 'DEVKEY-12', None), + (EXPECTED_DEFAULTS[0], 'DEVKEY-12', EXPECTED_DEFAULTS[2])), + (('SERVER-URL-13', 'DEVKEY-13', None), + ('SERVER-URL-13', 'DEVKEY-13', EXPECTED_DEFAULTS[2])), + ((None, None, 'Proxy-14'), + (EXPECTED_DEFAULTS[0], EXPECTED_DEFAULTS[1],'Proxy-14')), + (('SERVER-URL-15', 'DEVKEY-15', 'Proxy-15'), + ('SERVER-URL-15', 'DEVKEY-15', 'Proxy-15')) + ] + +@pytest.mark.parametrize("param_values, expectations", test_data_init_params) +def test_init_params(api_helper_class, monkeypatch, param_values, expectations): + """ init TestLinkHelper with parameter and no env variables """ + # unset TestLinkHelper related environment variables + setEnviron(monkeypatch, ENVNAMES[0], None) + setEnviron(monkeypatch, ENVNAMES[1], None) + setEnviron(monkeypatch, ENVNAMES[2], None) + # init helper with params + a_helper = api_helper_class(*param_values) + assert expectations == (a_helper._server_url, a_helper._devkey, + a_helper._proxy) + +def test_init_env_params(api_helper_class, monkeypatch): + """ init TestLinkHelper with mixed method parameter and env variables """ + # set TestLinkHelper related environment variables + setEnviron(monkeypatch, ENVNAMES[0], 'SERVER-URL-21') + setEnviron(monkeypatch, ENVNAMES[1], 'DEVKEY-21') + setEnviron(monkeypatch, ENVNAMES[2], 'PROXY-21') + # init helper with method params + a_helper = api_helper_class('SERVER-URL-22', 'DEVKEY-22', 'PROXY-22') + # the method params have a high priority than the environment variables + assert 'SERVER-URL-22' == a_helper._server_url + assert 'DEVKEY-22' == a_helper._devkey + assert 'PROXY-22' == a_helper._proxy - - def test_connect_with_proxy(self): - """ create a TestLink API dummy with ProxiedTransport""" - a_helper = self.CLASSUNDERTEST('SERVER-URL-71', 'DEVKEY-71', 'PROXY-71') - a_tl_api = a_helper.connect(DummyTestLinkAPI) - self.assertEqual('SERVER-URL-71', a_tl_api.server) - self.assertEqual('DEVKEY-71', a_tl_api.devKey) - self.assertEqual('PROXY-71', a_tl_api.args['transport'].proxy) - + +def test_createArgparser(api_helper_class): + """ create TestLinkHelper command line argument parser """ + a_helper = api_helper_class('SERVER-URL-31', 'DEVKEY-31', 'PROXY-31') + a_parser = a_helper._createArgparser('DESCRIPTION-31') + assert 'DESCRIPTION-31', a_parser.description + default_args=a_parser.parse_args('') + assert 'SERVER-URL-31' == default_args.server_url + assert 'DEVKEY-31' == default_args.devKey + assert 'PROXY-31' == default_args.proxy + +def test_setParamsFromArgs(api_helper_class): + """ set TestLinkHelper params from command line arguments """ + a_helper = api_helper_class() + a_helper.setParamsFromArgs(None, ['--server_url', 'SERVER-URL-41', + '--devKey' , 'DEVKEY-41']) + assert 'SERVER-URL-41' == a_helper._server_url + assert 'DEVKEY-41' == a_helper._devkey + +def test_connect(api_helper_class): + """ create a TestLink API dummy """ + a_helper = api_helper_class('SERVER-URL-51', 'DEVKEY-51') + a_tl_api = a_helper.connect(DummyTestLinkAPI) + assert 'SERVER-URL-51' == a_tl_api.server + assert 'DEVKEY-51' == a_tl_api.devKey + assert {} == a_tl_api.args + +def test_getProxiedTransport_py2(api_helper_class): + """ create a TestLink Helper with ProxiedTransport - py27 """ + if IS_PY3: + pytest.skip("py27 specific test") -if __name__ == "__main__": - #import sys;sys.argv = ['', 'Test.testName'] - unittest.main() \ No newline at end of file + a_helper = api_helper_class('SERVER-URL-611', 'DEVKEY-611', 'PROXY-611:8080') + #'http://fast.proxy.com.de/') + a_pt = a_helper._getProxiedTransport() + assert 'ProxiedTransport' == a_pt.__class__.__name__ + assert 'PROXY-611:8080' == a_pt.proxy + +def test_getProxiedTransport_py3(api_helper_class): + """ create a TestLink Helper with ProxiedTransport - py3x """ + + if not IS_PY3: + pytest.skip("py3 specific test") + + a_helper = api_helper_class('SERVER-URL-612', 'DEVKEY-612', 'http://PROXY-612:8080') + #'http://fast.proxy.com.de/') + a_pt = a_helper._getProxiedTransport() + assert 'ProxiedTransport' == a_pt.__class__.__name__ + assert ('http://proxy-612', 8080) == a_pt.proxy + +def test_connect_with_proxy2(api_helper_class): + """ create a TestLink API dummy with ProxiedTransport - py27""" + if IS_PY3: + pytest.skip("py27 specific test") + + a_helper = api_helper_class('SERVER-URL-711', 'DEVKEY-711', 'PROXY-711:8080') + a_tl_api = a_helper.connect(DummyTestLinkAPI) + assert 'SERVER-URL-711' == a_tl_api.server + assert 'DEVKEY-711' == a_tl_api.devKey + assert 'PROXY-711:8080' == a_tl_api.args['transport'].proxy + +def test_connect_with_proxy3(api_helper_class): + """ create a TestLink API dummy with ProxiedTransport - py3x""" + if not IS_PY3: + pytest.skip("py3 specific test") + + a_helper = api_helper_class('SERVER-URL-712', 'DEVKEY-712', 'https://PROXY-712:8080') + a_tl_api = a_helper.connect(DummyTestLinkAPI) + assert 'SERVER-URL-712' == a_tl_api.server + assert 'DEVKEY-712' == a_tl_api.devKey + assert ('https://proxy-712', 8080) == a_tl_api.args['transport'].proxy + +def test_connect_ignoring_proxy_env(api_helper_class, monkeypatch): + """ create a TestLink API dummy ignoring PROXY env - pullRequest #121 """ + setEnviron(monkeypatch, ENVNAMES[2], 'PROXY-71') + a_helper = api_helper_class('SERVER-URL-71', 'DEVKEY-71', False) + a_tl_api = a_helper.connect(DummyTestLinkAPI) + assert 'SERVER-URL-71' == a_tl_api.server + assert 'DEVKEY-71' == a_tl_api.devKey + assert {} == a_tl_api.args + + +def test_connect_with_https_no_context(api_helper_class): + """ create a TestLink API dummy for https with uncertified context """ + a_helper = api_helper_class('https://SERVER-URL-71', 'DEVKEY-71') + a_tl_api = a_helper.connect(DummyTestLinkAPI) + assert 'https://SERVER-URL-71' == a_tl_api.server + assert 'DEVKEY-71' == a_tl_api.devKey + a_context = a_tl_api.args['context'] + assert [] == a_context.get_ca_certs() + +def test_connect_with_https_and_context(api_helper_class): + """ create a TestLink API dummy for https with special context """ + a_helper = api_helper_class('https://SERVER-URL-71', 'DEVKEY-71') + a_tl_api = a_helper.connect(DummyTestLinkAPI, context='ssl_context') + assert 'https://SERVER-URL-71' == a_tl_api.server + assert 'DEVKEY-71' == a_tl_api.devKey + assert 'ssl_context' == a_tl_api.args['context'] + \ No newline at end of file diff --git a/test/utest-online/test_apiCall_differentPositionalArgs.py b/test/utest-online/test_apiCall_differentPositionalArgs.py new file mode 100644 index 0000000..3936071 --- /dev/null +++ b/test/utest-online/test_apiCall_differentPositionalArgs.py @@ -0,0 +1,107 @@ +#! /usr/bin/python +# -*- coding: UTF-8 -*- + +# Copyright 2012-2019 Luiko Czub, TestLink-API-Python-client developers +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------ + +# TestCases for Testlink API calls, where the Api Clients uses different +# positional arg configurations +# - TestlinkAPIClient, TestlinkAPIGeneric +# +# +# this test requires an online TestLink Server, which connection parameters +# are defined in environment variables +# TESTLINK_API_PYTHON_DEVKEY and TESTLINK_API_PYTHON_DEVKEY +# +# FIME LC 29.10.29: test does not really interacts with test link +# only negative test with none existing IDs implemented +# ok to check every implemented server call one time but not +# to cover all possible responses or argument combinations + +import pytest +from testlink.testlinkerrors import TLResponseError + +class Test_TestlinkAPIClient_Behaviour(): + ''' Test api call with positional arg configuration TestlinkAPIClient ''' + + def test_getLastExecutionResult_unknownID(self, api_general_client): + with pytest.raises(TLResponseError, match='3000.*40000711'): + api_general_client.getLastExecutionResult(40000711, 40000712) + + def test_getTestCaseCustomFieldDesignValue_unknownID(self, api_general_client): + with pytest.raises(TLResponseError, match='7000.*40000711'): + api_general_client.getTestCaseCustomFieldDesignValue( + 'TC-40000712', 1, 40000711, 'a_field', 'a_detail') + + def test_getTestCasesForTestSuite_unknownID(self, api_general_client): + with pytest.raises(TLResponseError, match='8000.*40000711'): + api_general_client.getTestCasesForTestSuite(40000711, 2, 'a_detail') + + def test_createBuild_unknownID(self, api_general_client): + with pytest.raises(TLResponseError, match='3000.*40000711'): + api_general_client.createBuild(40000711, 'Build 40000712', 'note 40000713') + + def test_createTestCase_unknownID(self, api_general_client): + with pytest.raises(TLResponseError, match='7000.*40000713'): + api_general_client.createTestCase('case 40000711', 40000712, 40000713, + 'Big Bird', 'summary 40000714') + + def test_reportTCResult_unknownID(self, api_general_client): + with pytest.raises(TLResponseError, match='5000.*40000711'): + api_general_client.reportTCResult(40000711, 40000712, 'build 40000713', 'p', + 'note 40000714') + + def test_uploadExecutionAttachment_unknownID(self, api_general_client, attachmentFile): + with pytest.raises(TLResponseError, match='6004.*40000712'): + api_general_client.uploadExecutionAttachment(attachmentFile, 40000712, + 'title 40000713', 'descr. 40000714') + +class Test_TestlinkAPIGeneric_Behaviour(): + ''' Test api call with positional arg configuration TestlinkAPIGeneric ''' + + def test_getLastExecutionResult_unknownID(self, api_generic_client): + with pytest.raises(TLResponseError, match='3000.*40000711'): + api_generic_client.getLastExecutionResult(40000711, testcaseid=40000712) + + def test_getTestCaseCustomFieldDesignValue_unknownID(self, api_generic_client): + with pytest.raises(TLResponseError, match='7000.*40000711'): + api_generic_client.getTestCaseCustomFieldDesignValue( + 'TC-40000712', 1, 40000711, 'a_field', details='full') + + def test_getTestCasesForTestSuite_unknownID(self, api_generic_client): + with pytest.raises(TLResponseError, match='8000.*40000711'): + api_generic_client.getTestCasesForTestSuite(40000711) + + def test_createBuild_unknownID(self, api_generic_client): + with pytest.raises(TLResponseError, match='3000.*40000711'): + api_generic_client.createBuild(40000711, 'Build 40000712', buildnotes='note 40000713') + + def test_createTestCase_unknownID(self, api_generic_client): + tc_steps = [] + with pytest.raises(TLResponseError, match='7000.*40000713'): + api_generic_client.createTestCase('case 40000711', 40000712, 40000713, + 'Big Bird', 'summary 40000714', tc_steps) + + def test_reportTCResult_unknownID(self, api_generic_client): + with pytest.raises(TLResponseError, match='5000.*40000711'): + api_generic_client.reportTCResult(40000712, 'p', testcaseid=40000711, + buildname='build 40000713', notes='note 40000714' ) + + def test_uploadExecutionAttachment_unknownID(self, api_generic_client, attachmentFile): + with pytest.raises(TLResponseError, match='6004.*40000712'): + api_generic_client.uploadExecutionAttachment(attachmentFile, 40000712, + title='title 40000713', description='descr. 40000714') + diff --git a/test/utest-online/test_apiCall_equalPositionalArgs.py b/test/utest-online/test_apiCall_equalPositionalArgs.py new file mode 100644 index 0000000..c133658 --- /dev/null +++ b/test/utest-online/test_apiCall_equalPositionalArgs.py @@ -0,0 +1,479 @@ +#! /usr/bin/python +# -*- coding: UTF-8 -*- + +# Copyright 2013-2021 Luiko Czub, TestLink-API-Python-client developers +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------ + +# TestCases for Testlink API calls, where the Api Clients uses equal +# positional arg configurations +# - TestlinkAPIClient, TestlinkAPIGeneric +# +# tests requires an online TestLink Server, which connection parameters +# are defined in environment variables +# TESTLINK_API_PYTHON_DEVKEY and TESTLINK_API_PYTHON_DEVKEY +# +# FIXME LC 29.10.29: test does not really interacts with test link +# only negative test with none existing IDs implemented +# ok to check every implemented server call one time but not +# to cover all possible responses or argument combinations + +import pytest +import re +from testlink.testlinkerrors import TLResponseError + +# test_ApiCall_UnknownKey_EqualBehaviour + +def test_checkDevKey(api_client): + assert True == api_client.checkDevKey() + +def test_checkDevKey_unknownKey(api_client): + with pytest.raises(TLResponseError, match='2000.*invalid') as excinfo: + api_client.checkDevKey(devKey='unknownKey') + +def test_sayHello(api_client): + assert 'Hello!' == api_client.sayHello() + +def test_repeat(api_client): + assert 'You said: Yellow Submarine' == api_client.repeat('Yellow Submarine') + +def test_about(api_client): + assert 'Testlink API' in api_client.about() + +def test_doesUserExist_unknownID(api_client): + with pytest.raises(TLResponseError, match='10000.*Big Bird'): + api_client.doesUserExist('Big Bird') + +def test_createTestProject_unknownID(api_client): + with pytest.raises(TLResponseError, match='7001.*Empty name'): + api_client.createTestProject(testprojectname='', + testcaseprefix='P40000711') + +def test_createTestProject_unknownITS(api_client): + with pytest.raises(TLResponseError, match='13000.*Unable to find'): + api_client.createTestProject(testprojectname='aProject', + testcaseprefix='aPrefix', itsname='unknownITS') + +def test_getProjects(api_client): + assert None is not api_client.getProjects() + +def test_createTestPlan_projectname_posArg_unknownID(api_client): + with pytest.raises(TLResponseError, match='7011.*40000712'): + api_client.createTestPlan('plan 40000711', 'project 40000712') + +def test_createTestSuite_unknownID(api_client): + with pytest.raises(TLResponseError, match='7000.*40000711'): + api_client.createTestSuite( 40000711, 'suite 40000712', 'detail 40000713') + +# see test_apicall_differentPositionalArgs +# def test_createTestCase_unknownID(api_client): +# tc_steps = [] +# with pytest.raises(TLResponseError, match='7000.*40000713'): +# api_client.createTestCase('case 40000711', 40000712, 40000713, +# 'Big Bird', 'summary 40000714', tc_steps) + +def test_getBuildsForTestPlan_unknownID(api_client): + with pytest.raises(TLResponseError, match='3000.*40000711'): + api_client.getBuildsForTestPlan(40000711) + +def test_getFirstLevelTestSuitesForTestProject_unknownID(api_client): + with pytest.raises(TLResponseError, match='7000.*40000711'): + api_client.getFirstLevelTestSuitesForTestProject(40000711) + +def test_getFullPath_unknownID(api_client): + with pytest.raises(TLResponseError, match='getFullPath.*234'): + api_client.getFullPath('40000711') + +# see test_apicall_differentPositionalArgs +# def test_getLastExecutionResult_unknownID(api_client): +# with pytest.raises(TLResponseError, match='3000.*40000711'): +# api_client.getLastExecutionResult(40000711, testcaseid=40000712) + +def test_getLatestBuildForTestPlan_unknownID(api_client): + with pytest.raises(TLResponseError, match='3000.*40000711'): + api_client.getLatestBuildForTestPlan(40000711) + +def test_getProjectTestPlans_unknownID(api_client): + with pytest.raises(TLResponseError, match='7000.*40000711'): + api_client.getProjectTestPlans(40000711) + +def test_getProjectPlatforms_unknownID(api_client): + with pytest.raises(TLResponseError, match='7000.*40000711'): + api_client.getProjectPlatforms(40000711) + +def test_getTestCase_unknownID(api_client): + with pytest.raises(TLResponseError, match='5000.*40000711'): + api_client.getTestCase(testcaseid=40000711) + +def test_getTestCase_unknownExternalID(api_client): + with pytest.raises(TLResponseError, match='5040.*GPROAPI-40000711'): + api_client.getTestCase(testcaseexternalid='GPROAPI-40000711') + +def test_getTestCaseAttachments_unknownID(api_client): + with pytest.raises(TLResponseError, match='5000.*40000711'): + api_client.getTestCaseAttachments(testcaseid=40000711) + +# see test_apicall_differentPositionalArgs +# def test_getTestCaseCustomFieldDesignValue_unknownID(api_client): +# with pytest.raises(TLResponseError, match='7000.*40000711'): +# api_client.getTestCaseCustomFieldDesignValue( +# 'TC-40000712', 1, 40000711, 'a_field', details='full') + +def test_getTestCaseIDByName_unknownID(api_client): + with pytest.raises(TLResponseError, match='5030.*Cannot find'): + api_client.getTestCaseIDByName('Big Bird') + +def test_getTestCasesForTestPlan_unknownID(api_client): + with pytest.raises(TLResponseError, match='3000.*40000711'): + api_client.getTestCasesForTestPlan(40000711) + +# see test_apicall_differentPositionalArgs +# def test_getTestCasesForTestSuite_unknownID(api_client): +# with pytest.raises(TLResponseError, match='8000.*40000711'): +# api_client.getTestCasesForTestSuite(40000711) + +def test_getTestPlanByName_unknownID(api_client): + with pytest.raises(TLResponseError, match='7011.*40000711'): + api_client.getTestPlanByName('project 40000711', 'plan 40000712') + +def test_getTestPlanPlatforms_unknownID(api_client): + with pytest.raises(TLResponseError, match='3000.*40000711'): + api_client.getTestPlanPlatforms(40000711) + +def test_getTestProjectByName_unknownID(api_client): + with pytest.raises(TLResponseError, match='7011.*40000711'): + api_client.getTestProjectByName('project 40000711') + +def test_getTestSuiteByID_unknownID(api_client): + with pytest.raises(TLResponseError, match='8000.*40000711'): + api_client.getTestSuiteByID(40000711) + +def test_getTestSuitesForTestPlan_unknownID(api_client): + with pytest.raises(TLResponseError, match='3000.*40000711'): + api_client.getTestSuitesForTestPlan(40000711) + +def test_getTestSuitesForTestSuite_unknownID(api_client): + with pytest.raises(TLResponseError, match='8000.*40000711'): + api_client.getTestSuitesForTestSuite(40000711) + +def test_getTotalsForTestPlan_unknownID(api_client): + with pytest.raises(TLResponseError, match='3000.*40000711'): + api_client.getTotalsForTestPlan(40000711) + +# see test_apicall_differentPositionalArgs +# def test_createBuild_unknownID(api_client): +# with pytest.raises(TLResponseError, match='3000.*40000711'): +# api_client.createBuild(40000711, 'Build 40000712', buildnotes='note 40000713') + +# see test_apicall_differentPositionalArgs +# def test_reportTCResult_unknownID(api_client): +# with pytest.raises(TLResponseError, match='5000.*40000711'): +# api_client.reportTCResult(40000712, 'p', testcaseid=40000711, +# buildname='build 40000713', notes='note 40000714' ) + +# see test_apicall_differentPositionalArgs +# def test_uploadExecutionAttachment_unknownID(api_client, attachmentFile): +# with pytest.raises(TLResponseError, match='6004.*40000712'): +# api_client.uploadExecutionAttachment(attachmentFile, 40000712, +# title='title 40000713', description='descr. 40000714') + +def test_createPlatform_unknownID(api_client): + with pytest.raises(TLResponseError, match='7011.*40000711'): + api_client.createPlatform('Project 40000711', 'Platform 40000712', + notes='note 40000713', + platformondesign=True, + platformonexecution=True) + +def test_addPlatformToTestPlan_unknownID(api_client): + with pytest.raises(TLResponseError, match='3000.*40000711'): + api_client.addPlatformToTestPlan(40000711, 'Platform 40000712') + +def test_removePlatformFromTestPlan_unknownID(api_client): + with pytest.raises(TLResponseError, match='3000.*40000711'): + api_client.removePlatformFromTestPlan(40000711, 'Platform 40000712') + +def test_addTestCaseToTestPlan_unknownID(api_client): + with pytest.raises(TLResponseError, match='7000.*40000711'): + api_client.addTestCaseToTestPlan(40000711, 40000712, 'N-40000713', 1) + +def test_updateTestCase_unknownID(api_client): + with pytest.raises(TLResponseError, match='5040.*N-40000711'): + api_client.updateTestCase('N-40000711', version=1) + +def test_createTestCaseSteps_unknownID(api_client): + steps = [{'actions' : "Step action 6 -b added by updateTestCase" , + 'expected_results' : "Step result 6 - b added", + 'step_number' : 6, 'execution_type' : 1}] + with pytest.raises(TLResponseError, match='5040.*N-40000711'): + api_client.createTestCaseSteps('update', steps, + testcaseexternalid='N-40000711', version=1) + +def test_deleteTestCaseSteps_unknownID(api_client): + steps = [2,8] + with pytest.raises(TLResponseError, match='5040.*N-40000711'): + api_client.deleteTestCaseSteps('N-40000711', steps, version=1) + +def test_uploadRequirementSpecificationAttachment_unknownID(api_client, attachmentFile): + with pytest.raises(TLResponseError, match='6004.*40000712'): + api_client.uploadRequirementSpecificationAttachment(attachmentFile, 40000712, + title='title 40000713', description='descr. 40000714') + +def test_uploadRequirementAttachment_unknownID(api_client, attachmentFile): + with pytest.raises(TLResponseError, match='6004.*40000712'): + api_client.uploadRequirementAttachment(attachmentFile, 40000712, + title='title 40000713', description='descr. 40000714') + +def test_uploadTestProjectAttachment_unknownID(api_client, attachmentFile): + with pytest.raises(TLResponseError, match='7000.*40000712'): + api_client.uploadTestProjectAttachment(attachmentFile, 40000712, + title='title 40000713', description='descr. 40000714') + +def test_uploadTestSuiteAttachment_unknownID(api_client, attachmentFile): + with pytest.raises(TLResponseError, match='8000.*40000712'): + api_client.uploadTestSuiteAttachment(attachmentFile, 40000712, + title='title 40000713', description='descr. 40000714') + +def test_uploadTestCaseAttachment_unknownID(api_client, attachmentFile): + with pytest.raises(TLResponseError, match='5000.*testcaseid'): + api_client.uploadTestCaseAttachment(attachmentFile, 40000712, 1, + title='title 40000713', description='descr. 40000714') + +def test_uploadAttachment_unknownID(api_client, attachmentFile): + with pytest.raises(TLResponseError, match='6004.*Invalid Foreign Key ID'): + api_client.uploadAttachment(attachmentFile, '0000', 'nodes_hierarchy', + title='title 40000713', description='descr. 40000714') + +def test_testLinkVersion(api_client): + assert re.match(r'\d*\.\d*\.\d*', api_client.testLinkVersion() ) + +def test_getUserByLogin_unknownKey(api_client): + with pytest.raises(TLResponseError, match='10000.*User Login'): + api_client.getUserByLogin('unknownUser') + +def test_getUserByID_unknownKey(api_client): + with pytest.raises(TLResponseError, match='NO_USER_BY_ID_LOGIN.*User with DB ID'): + api_client.getUserByID(40000711) + +@pytest.mark.xfail(reason='setTestMode not implemented for python client' ) +def test_setTestMode(api_client): + assert api_client.setTestMode(True) + assert not api_client.setTestMode(False) + +def test_deleteExecution_unknownKey(api_client): + try: + # case: TL configuration allows deletion of executions + # response returns Success, even if executionID is unkown + expected = [{'status': True, 'message': 'Success!', 'id': 40000711, + 'operation': 'deleteExecution'}] + assert expected == api_client.deleteExecution(40000711) + except TLResponseError as tl_err: + # case: TL configuration does not allow deletion of executions + # Expects: 232: Configuration does not allow delete executions + assert 232 == tl_err.code + +def test_setTestCaseExecutionType_unknownID(api_client): + with pytest.raises(TLResponseError, match='7000.*40000712'): + api_client.setTestCaseExecutionType('N-40000711', 1, 40000712, 1) + +def test_assignRequirements_unknownID(api_client): + with pytest.raises(TLResponseError, match='7000.*40000712'): + api_client.assignRequirements('N-40000711', 40000712, + [{'req_spec' : 40000713, 'requirements' : [40000714, 40000717]}, + {'req_spec' : 4723, 'requirements' : [4725]}]) + +def test_getExecCountersByBuild_unknownID(api_client): + with pytest.raises(TLResponseError, match='3000.*40000711'): + api_client.getExecCountersByBuild(40000711) + +def test_getTestCaseCustomFieldExecutionValue_unknownID(api_client): + with pytest.raises(TLResponseError, match='236.*version/executionid'): + api_client.getTestCaseCustomFieldExecutionValue( + 'cf_full', '40000711', 1, '715', '40000713') + +def test_getTestCaseCustomFieldTestPlanDesignValue_unknownID(api_client): + with pytest.raises(TLResponseError, match='7000.*40000711'): + api_client.getTestCaseCustomFieldTestPlanDesignValue( + 'cf_full', '40000711', 1, '40000713', '615') + +def test_updateTestCaseCustomFieldDesignValue_unknownID(api_client): + with pytest.raises(TLResponseError, match='7000.*40000711'): + api_client.updateTestCaseCustomFieldDesignValue( + 'TC-40000712', 1, 40000711, {'cf_field1' : 'value1', + 'cf_field2' : 'value2'}) + +def test_getTestSuiteCustomFieldDesignValue_unknownID(api_client): + with pytest.raises(TLResponseError, match='7000.*40000711'): + api_client.getTestSuiteCustomFieldDesignValue( + 'cf_full', 40000711, 40000713) + +def test_getTestPlanCustomFieldDesignValue_unknownID(api_client): + with pytest.raises(TLResponseError, match='7000.*40000711'): + api_client.getTestPlanCustomFieldDesignValue( + 'cf_full', 40000711, 40000712) + +def test_getReqSpecCustomFieldDesignValue_unknownID(api_client): + with pytest.raises(TLResponseError, match='7000.*40000711'): + api_client.getReqSpecCustomFieldDesignValue( + 'cf_full', 40000711, 4732) + +def test_getRequirementCustomFieldDesignValue_unknownID(api_client): + with pytest.raises(TLResponseError, match='7000.*40000711'): + api_client.getRequirementCustomFieldDesignValue( + 'cf_full', 40000711, 4734) + +def test_assignTestCaseExecutionTask_unknownID(api_client): + with pytest.raises(TLResponseError, match='3000.*40000711'): + api_client.assignTestCaseExecutionTask('username', 40000711, 'TC-40000712', + buildname='build 40000713', + platformname='platform 40000714') + +def test_getTestCaseBugs_unknownID(api_client): + with pytest.raises(TLResponseError, match='3000.*40000711'): + api_client.getTestCaseBugs(40000711, testcaseexternalid='TC-40000712', + buildname='build 40000713', + platformname='platform 40000714') + +def test_getTestCaseAssignedTester_unknownID(api_client): + with pytest.raises(TLResponseError, match='3000.*40000711'): + api_client.getTestCaseAssignedTester(40000711, 'TC-40000712', + buildname='build 40000713', + platformname='platform 40000714') + +def test_unassignTestCaseExecutionTask_unknownID(api_client): + with pytest.raises(TLResponseError, match='3000.*40000711'): + api_client.unassignTestCaseExecutionTask(40000711, 'TC-40000712', + buildname='build 40000713', + platformname='platform 40000714', + user='username',action='unassignOne') + +def test_getProjectKeywords_unknownID(api_client): + with pytest.raises(TLResponseError, match='7000.*40000711'): + api_client.getProjectKeywords(40000711) + +def test_getTestCaseKeywords_unknownID(api_client): + with pytest.raises(TLResponseError, match='5040.*40000712'): + api_client.getTestCaseKeywords(testcaseid=40000712) + +def test_getTestCaseKeywords_unknownID_set(api_client): + with pytest.raises(TLResponseError, match='5040.*40000712'): + api_client.getTestCaseKeywords(testcaseid=[40000712, 40000713]) + +def test_getTestCaseKeywords_unknownID_external_single(api_client): + with pytest.raises(TLResponseError, match='5040.*TC-40000712'): + api_client.getTestCaseKeywords(testcaseexternalid='TC-40000712') + +def test_getTestCaseKeywords_unknownID_external_set(api_client): + with pytest.raises(TLResponseError, match='5040.*TC-40000712'): + api_client.getTestCaseKeywords(testcaseexternalid=['TC-40000712', 'TC-40000713']) + +def test_deleteTestPlan_unknownID(api_client): + with pytest.raises(TLResponseError, match='3000.*40000711'): + api_client.deleteTestPlan(40000711) + +def test_addTestCaseKeywords_unknownID(api_client): + with pytest.raises(TLResponseError, match='5040.*TC-40000712'): + api_client.addTestCaseKeywords({'TC-40000712' : + ['KeyWord01', 'KeyWord03']}) + +def test_removeTestCaseKeywords_unknownID(api_client): + with pytest.raises(TLResponseError, match='5040.*TC-40000712'): + api_client.removeTestCaseKeywords({'TC-40000712' : ['KeyWord01']}) + +def test_deleteTestProject_unknownID(api_client): + with pytest.raises(TLResponseError, match='7013.*TProjectPrefix'): + api_client.deleteTestProject('TProjectPrefix') + +def test_createTestPlan_projectname_optArg_unknownID(api_client): + with pytest.raises(TLResponseError, match='7011.*40000712'): + api_client.createTestPlan('plan 40000711', + testprojectname='project 40000712') + +def test_createTestPlan_prefix_unknownID(api_client): + with pytest.raises(TLResponseError, match='NO.*TProjectPrefix'): + api_client.createTestPlan('plan 40000713', + prefix='TProjectPrefix') + +def test_updateTestSuiteCustomFieldDesignValue_unknownID(api_client): + with pytest.raises(TLResponseError, match='7000.*40000712'): + api_client.updateTestSuiteCustomFieldDesignValue( + '40000712 TP-ID', '40000711 TS-ID', + {'cf_tc_ex_string' : 'a custom exec value', + 'cf_tc_ex_numeric' : 111} ) + +def test_getTestSuite_unknownID(api_client): + with pytest.raises(TLResponseError, match='NO.*TProjectPrefix'): + api_client.getTestSuite('suite 40000712', 'TProjectPrefix') + + +def test_updateTestSuite_unknownID(api_client): + with pytest.raises(TLResponseError, match='7000.*40000711'): + api_client.updateTestSuite(40000712, testprojectid=40000711, + testsuitename = 'suite 40000712 updated', + details = 'detail 40000713 updated', + order =1) + +def test_getIssueTrackerSystem_unknownITS(api_client): + with pytest.raises(TLResponseError, match='13000.*Unable to find'): + api_client.getIssueTrackerSystem('unknownITS') + +def test_updateBuildCustomFieldsValues_unknownID(api_client): + with pytest.raises(TLResponseError, match='7000.*40000712'): + api_client.updateBuildCustomFieldsValues( + '40000712 project', '40000713 plan', '40000714 build', + {'cf_b_ex_string' : 'a custom exec value', + 'cf_b_ex_numeric' : 111} ) + +def test_getExecutionSet_unknownID(api_client): + with pytest.raises(TLResponseError, match='3000.*40000713'): + api_client.getExecutionSet( + '40000713 plan', testcaseexternalid = 'TC-40000712') + +def test_getRequirements_unknownID(api_client): + with pytest.raises(TLResponseError, match='7000.*40000712'): + api_client.getRequirements( + '40000712 project', + testplanid = '40000713 plan', + platformid = '40000714 platform') + +def test_getReqCoverage_unknownID(api_client): + with pytest.raises(TLResponseError, match='7000.*40000712'): + api_client.getReqCoverage( + '40000712 project', '40000721 req') + +def test_setTestCaseTestSuite_unknownID(api_client): + with pytest.raises(TLResponseError, match='5040.*TC-40000712'): + api_client.setTestCaseTestSuite( + 'TC-40000712', '40000713 suite') + +def test_getTestSuiteAttachments_unknownID(api_client): + with pytest.raises(TLResponseError, match='8000.*40000712'): + api_client.getTestSuiteAttachments(40000712) + +def test_getAllExecutionsResults_unknownID(api_client): + with pytest.raises(TLResponseError, match='3000.*40000711'): + api_client.getAllExecutionsResults(40000711) + +def test_closeBuild_unknownID(api_client): + with pytest.raises(TLResponseError, match='4000.*40000713'): + api_client.closeBuild(40000713) + +def test_createUser_invalidMail(api_client): + with pytest.raises(TLResponseError, match='14003: Email.*seems no good'): + api_client.createUser('myLogin','myFname','myLname', 'myInvalidMail') + + +# if __name__ == "__main__": +# #import sys;sys.argv = ['', 'Test.testName'] +# unittest.main() \ No newline at end of file diff --git a/test/utest-online/testlinkapi_callserver_test.py b/test/utest-online/testlinkapi_callserver_test.py index 845c0be..2cf365e 100644 --- a/test/utest-online/testlinkapi_callserver_test.py +++ b/test/utest-online/testlinkapi_callserver_test.py @@ -1,7 +1,7 @@ #! /usr/bin/python # -*- coding: UTF-8 -*- -# Copyright 2012-2015 Luiko Czub, TestLink-API-Python-client developers +# Copyright 2012-2019 Luiko Czub, TestLink-API-Python-client developers # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -21,59 +21,43 @@ # are defined in environment variables # TESTLINK_API_PYTHON_SERVER_URL and TESTLINK_API_PYTHON_DEVKEY -import unittest -from testlink import TestlinkAPIClient, TestLinkHelper +import pytest from testlink import testlinkerrors +def test_callServer_noArgs(api_client): + """ test _callServer() - calling method with no args """ + + assert 'Hello!' == api_client._callServer('sayHello') + +def test_callServer_withArgs(api_client): + """ test _callServer() - calling method with args """ + + assert 'You said: some arg' == api_client._callServer('repeat', {'str': 'some arg'}) + +def test_callServer_ProtocolError(api_client_class, api_helper_class): + """ test _callServer() - Server raises ProtocollError """ + + server_url = api_helper_class()._server_url + bad_server_url = server_url.split('xmlrpc.php')[0] + api_client = api_helper_class(bad_server_url).connect(api_client_class) + + with pytest.raises(testlinkerrors.TLConnectionError, + match='ProtocolError'): + api_client._callServer('sayHello') + +def test_callServer_socketError(api_client_class, api_helper_class): + """ test _callServer() - Server raises a socket Error (getaddrinfo failed) """ + + bad_server_url = 'http://111.222.333.4/testlink/lib/api/xmlrpc.php' + api_client = api_helper_class(bad_server_url).connect(api_client_class) + + with pytest.raises(testlinkerrors.TLConnectionError, + match='getaddrinfo failed'): + api_client._callServer('sayHello') + +def test_callServer_FaultError(api_client): + """ test _callServer() - Server raises Fault Error """ + + with pytest.raises(testlinkerrors.TLAPIError): + api_client._callServer('sayGoodBye') -class TestLinkAPIcallServerTestCase(unittest.TestCase): - """ TestCases for TestLinkAPICleint._callServer() """ - - def test_callServer_noArgs(self): - """ test _callServer() - calling method with no args """ - - client = TestLinkHelper().connect(TestlinkAPIClient) - response = client._callServer('sayHello') - self.assertEqual('Hello!', response) - - def test_callServer_withArgs(self): - """ test _callServer() - calling method with args """ - - client = TestLinkHelper().connect(TestlinkAPIClient) - response = client._callServer('repeat', {'str': 'some arg'}) - self.assertEqual('You said: some arg', response) - - def test_callServer_ProtocolError(self): - """ test _callServer() - Server raises ProtocollError """ - - server_url = TestLinkHelper()._server_url - bad_server_url = server_url.split('xmlrpc.php')[0] - client = TestLinkHelper(bad_server_url).connect(TestlinkAPIClient) - - def a_func(api_client): - api_client._callServer('sayHello') - self.assertRaises(testlinkerrors.TLConnectionError, a_func, client) - - def test_callServer_socketError(self): - """ test _callServer() - Server raises a socket Error (IOError) """ - - bad_server_url = 'http://111.222.333.4/testlink/lib/api/xmlrpc.php' - client = TestLinkHelper(bad_server_url).connect(TestlinkAPIClient) - - def a_func(api_client): - api_client._callServer('sayHello') - self.assertRaises(testlinkerrors.TLConnectionError, a_func, client) - - def test_callServer_FaultError(self): - """ test _callServer() - Server raises Fault Error """ - - client = TestLinkHelper().connect(TestlinkAPIClient) - - def a_func(api_client): - api_client._callServer('sayGoodBye') - self.assertRaises(testlinkerrors.TLAPIError, a_func, client) - - -if __name__ == "__main__": - #import sys;sys.argv = ['', 'Test.testName'] - unittest.main() \ No newline at end of file diff --git a/test/utest-online/testlinkapi_generic_online_test.py b/test/utest-online/testlinkapi_generic_online_test.py deleted file mode 100644 index 5317aac..0000000 --- a/test/utest-online/testlinkapi_generic_online_test.py +++ /dev/null @@ -1,447 +0,0 @@ -#! /usr/bin/python -# -*- coding: UTF-8 -*- - -# Copyright 2013-2015 Luiko Czub, TestLink-API-Python-client developers -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# ------------------------------------------------------------------------ - -# this test requires an online TestLink Server, which connection parameters -# are defined in environment variables -# TESTLINK_API_PYTHON_DEVKEY and TESTLINK_API_PYTHON_DEVKEY -# -# works with the example project NEW_PROJECT_API_GENERIC -# (see TestLinkExampleGenericApi.py) -# FIME LC 29.10.29: test does not really interacts with test link -# only negative test with none existing IDs implemented -# ok to check every implemented server call one time but not -# to cover all possible responses or argument combinations - -import sys -import os.path - -if sys.version_info[0] == 2 and sys.version_info[1] == 6: - # py26 needs backport unittest2 - import unittest2 as unittest -else: - import unittest - -if sys.version_info[0] == 2 and sys.version_info[1] == 7: - # py27 and py31 assertRaisesRegexp was renamed in py32 to assertRaisesRegex - unittest.TestCase.assertRaisesRegex = unittest.TestCase.assertRaisesRegexp - # py27 and py31 assertRegexpMatches was renamed in py32 to assertRegex - unittest.TestCase.assertRegex = unittest.TestCase.assertRegexpMatches - - -from testlink import TestlinkAPIGeneric, TestLinkHelper -from testlink.testlinkerrors import TLResponseError - -# example text file attachment = this python file -# why not using os.path.realpath(__file__) -# -> cause __file__ could be compiled python file *.pyc, if the test run is -# repeated without changing the test code -ATTACHMENT_EXAMPLE_TEXT= os.path.join(os.path.dirname(__file__), - 'testlinkapi_generic_online_test.py') - -class TestLinkAPIGenericOnlineTestCase(unittest.TestCase): - """ TestCases for TestlinkAPIClient - interacts with a TestLink Server. - works with the example project NEW_PROJECT_API (see TestLinkExample.py) - """ - - def setUp(self): - self.client = TestLinkHelper().connect(TestlinkAPIGeneric) - -# def tearDown(self): -# pass - - def test_checkDevKey(self): - response = self.client.checkDevKey() - self.assertEqual(True, response) - - def test_checkDevKey_unknownKey(self): - with self.assertRaisesRegex(TLResponseError, '2000.*invalid'): - self.client.checkDevKey(devKey='unknownKey') - - def test_sayHello(self): - response = self.client.sayHello() - self.assertEqual('Hello!', response) - - def test_repeat(self): - response = self.client.repeat('Yellow Submarine') - self.assertEqual('You said: Yellow Submarine', response) - - def test_about(self): - response = self.client.about() - self.assertIn('Testlink API', response) - - def test_doesUserExist_unknownID(self): - with self.assertRaisesRegex(TLResponseError, '10000.*Big Bird'): - self.client.doesUserExist('Big Bird') - - def test_createTestProject_unknownID(self): - with self.assertRaisesRegex(TLResponseError, '7001.*Empty name'): - self.client.createTestProject(testprojectname='', - testcaseprefix='P40000711') - - def test_getProjects(self): - response = self.client.getProjects() - self.assertIsNotNone(response) - - def test_createTestPlan_projectname_posArg_unknownID(self): - with self.assertRaisesRegex(TLResponseError, '7011.*40000712'): - self.client.createTestPlan('plan 40000711', 'project 40000712') - - def test_createTestSuite_unknownID(self): - with self.assertRaisesRegex(TLResponseError, '7000.*40000711'): - self.client.createTestSuite( 40000711, 'suite 40000712', 'detail 40000713') - - def test_createTestCase_unknownID(self): - tc_steps = [] - with self.assertRaisesRegex(TLResponseError, '7000.*40000713'): - self.client.createTestCase('case 40000711', 40000712, 40000713, - 'Big Bird', 'summary 40000714', tc_steps) - - def test_getBuildsForTestPlan_unknownID(self): - with self.assertRaisesRegex(TLResponseError, '3000.*40000711'): - self.client.getBuildsForTestPlan(40000711) - - def test_getFirstLevelTestSuitesForTestProject_unknownID(self): - with self.assertRaisesRegex(TLResponseError, '7000.*40000711'): - self.client.getFirstLevelTestSuitesForTestProject(40000711) - - def test_getFullPath_unknownID(self): - with self.assertRaisesRegex(TLResponseError, 'getFullPath.*234'): - self.client.getFullPath('40000711') - - def test_getLastExecutionResult_unknownID(self): - with self.assertRaisesRegex(TLResponseError, '3000.*40000711'): - self.client.getLastExecutionResult(40000711, testcaseid=40000712) - - def test_getLatestBuildForTestPlan_unknownID(self): - with self.assertRaisesRegex(TLResponseError, '3000.*40000711'): - self.client.getLatestBuildForTestPlan(40000711) - - def test_getProjectTestPlans_unknownID(self): - with self.assertRaisesRegex(TLResponseError, '7000.*40000711'): - self.client.getProjectTestPlans(40000711) - - def test_getProjectPlatforms_unknownID(self): - with self.assertRaisesRegex(TLResponseError, '7000.*40000711'): - self.client.getProjectPlatforms(40000711) - - def test_getTestCase_unknownID(self): - with self.assertRaisesRegex(TLResponseError, '5000.*40000711'): - self.client.getTestCase(testcaseid=40000711) - - def test_getTestCase_unknownExternalID(self): - with self.assertRaisesRegex(TLResponseError, '5040.*GPROAPI-40000711'): - self.client.getTestCase(testcaseexternalid='GPROAPI-40000711') - - def test_getTestCaseAttachments_unknownID(self): - with self.assertRaisesRegex(TLResponseError, '5000.*40000711'): - self.client.getTestCaseAttachments(testcaseid=40000711) - - def test_getTestCaseCustomFieldDesignValue_unknownID(self): - with self.assertRaisesRegex(TLResponseError, '7000.*40000711'): - self.client.getTestCaseCustomFieldDesignValue( - 'TC-40000712', 1, 40000711, 'a_field', details='full') - - def test_getTestCaseIDByName_unknownID(self): - with self.assertRaisesRegex(TLResponseError, '5030.*Cannot find'): - self.client.getTestCaseIDByName('Big Bird') - - def test_getTestCasesForTestPlan_unknownID(self): - with self.assertRaisesRegex(TLResponseError, '3000.*40000711'): - self.client.getTestCasesForTestPlan(40000711) - - def test_getTestCasesForTestSuite_unknownID(self): - with self.assertRaisesRegex(TLResponseError, '8000.*40000711'): - self.client.getTestCasesForTestSuite(40000711) - - def test_getTestPlanByName_unknownID(self): - with self.assertRaisesRegex(TLResponseError, '7011.*40000711'): - self.client.getTestPlanByName('project 40000711', 'plan 40000712') - - def test_getTestPlanPlatforms_unknownID(self): - with self.assertRaisesRegex(TLResponseError, '3000.*40000711'): - self.client.getTestPlanPlatforms(40000711) - - def test_getTestProjectByName_unknownID(self): - with self.assertRaisesRegex(TLResponseError, '7011.*40000711'): - self.client.getTestProjectByName('project 40000711') - - def test_getTestSuiteByID_unknownID(self): - with self.assertRaisesRegex(TLResponseError, '8000.*40000711'): - self.client.getTestSuiteByID(40000711) - - def test_getTestSuitesForTestPlan_unknownID(self): - with self.assertRaisesRegex(TLResponseError, '3000.*40000711'): - self.client.getTestSuitesForTestPlan(40000711) - - def test_getTestSuitesForTestSuite_unknownID(self): - with self.assertRaisesRegex(TLResponseError, '8000.*40000711'): - self.client.getTestSuitesForTestSuite(40000711) - - def test_getTotalsForTestPlan_unknownID(self): - with self.assertRaisesRegex(TLResponseError, '3000.*40000711'): - self.client.getTotalsForTestPlan(40000711) - - def test_createBuild_unknownID(self): - with self.assertRaisesRegex(TLResponseError, '3000.*40000711'): - self.client.createBuild(40000711, 'Build 40000712', buildnotes='note 40000713') - - def test_reportTCResult_unknownID(self): - with self.assertRaisesRegex(TLResponseError, '5000.*40000711'): - self.client.reportTCResult(40000712, 'p', testcaseid=40000711, - buildname='build 40000713', notes='note 40000714' ) - - def test_uploadExecutionAttachment_unknownID(self): - attachemantFile = open(os.path.realpath(__file__), 'r') - with self.assertRaisesRegex(TLResponseError, '6004.*40000712'): - self.client.uploadExecutionAttachment(attachemantFile, 40000712, - title='title 40000713', description='descr. 40000714') - - def test_createPlatform_unknownID(self): - with self.assertRaisesRegex(TLResponseError, '7011.*40000711'): - self.client.createPlatform('Project 40000711', 'Platform 40000712', - notes='note 40000713') - - def test_addPlatformToTestPlan_unknownID(self): - with self.assertRaisesRegex(TLResponseError, '3000.*40000711'): - self.client.addPlatformToTestPlan(40000711, 'Platform 40000712') - - def test_removePlatformFromTestPlan_unknownID(self): - with self.assertRaisesRegex(TLResponseError, '3000.*40000711'): - self.client.removePlatformFromTestPlan(40000711, 'Platform 40000712') - - def test_addTestCaseToTestPlan_unknownID(self): - with self.assertRaisesRegex(TLResponseError, '7000.*40000711'): - self.client.addTestCaseToTestPlan(40000711, 40000712, 'N-40000713', 1) - - def test_updateTestCase_unknownID(self): - with self.assertRaisesRegex(TLResponseError, '5040.*N-40000711'): - self.client.updateTestCase('N-40000711', version=1) - - def test_createTestCaseSteps_unknownID(self): - steps = [{'actions' : "Step action 6 -b added by updateTestCase" , - 'expected_results' : "Step result 6 - b added", - 'step_number' : 6, 'execution_type' : 1}] - with self.assertRaisesRegex(TLResponseError, '5040.*N-40000711'): - self.client.createTestCaseSteps('update', steps, - testcaseexternalid='N-40000711', version=1) - - def test_deleteTestCaseSteps_unknownID(self): - steps = [2,8] - with self.assertRaisesRegex(TLResponseError, '5040.*N-40000711'): - self.client.deleteTestCaseSteps('N-40000711', steps, version=1) - - def test_uploadRequirementSpecificationAttachment_unknownID(self): - attachemantFile = open(ATTACHMENT_EXAMPLE_TEXT, 'r') - with self.assertRaisesRegex(TLResponseError, '6004.*40000712'): - self.client.uploadRequirementSpecificationAttachment(attachemantFile, 40000712, - title='title 40000713', description='descr. 40000714') - - def test_uploadRequirementAttachment_unknownID(self): - attachemantFile = open(ATTACHMENT_EXAMPLE_TEXT, 'r') - with self.assertRaisesRegex(TLResponseError, '6004.*40000712'): - self.client.uploadRequirementAttachment(attachemantFile, 40000712, - title='title 40000713', description='descr. 40000714') - - def test_uploadTestProjectAttachment_unknownID(self): - attachemantFile = open(ATTACHMENT_EXAMPLE_TEXT, 'r') - with self.assertRaisesRegex(TLResponseError, '7000.*40000712'): - self.client.uploadTestProjectAttachment(attachemantFile, 40000712, - title='title 40000713', description='descr. 40000714') - - def test_uploadTestSuiteAttachment_unknownID(self): - attachemantFile = open(ATTACHMENT_EXAMPLE_TEXT, 'r') - with self.assertRaisesRegex(TLResponseError, '8000.*40000712'): - self.client.uploadTestSuiteAttachment(attachemantFile, 40000712, - title='title 40000713', description='descr. 40000714') - - def test_uploadTestCaseAttachment_unknownID(self): - attachemantFile = open(ATTACHMENT_EXAMPLE_TEXT, 'r') - with self.assertRaisesRegex(TLResponseError, '5000.*testcaseid'): - self.client.uploadTestCaseAttachment(attachemantFile, 40000712, - title='title 40000713', description='descr. 40000714') - - def test_uploadAttachment_unknownID(self): - attachemantFile = open(ATTACHMENT_EXAMPLE_TEXT, 'r') - with self.assertRaisesRegex(TLResponseError, '6004.*Invalid Foreign Key ID'): - self.client.uploadAttachment(attachemantFile, '0000', 'nodes_hierarchy', - title='title 40000713', description='descr. 40000714') - - def test_testLinkVersion(self): - response = self.client.testLinkVersion() - self.assertRegex(response, '\d*\.\d*\.\d*') - - def test_getUserByLogin_unknownKey(self): - with self.assertRaisesRegex(TLResponseError, '10000.*User Login'): - self.client.getUserByLogin('unknownUser') - - def test_getUserByID_unknownKey(self): - with self.assertRaisesRegex(TLResponseError, 'NO_USER_BY_ID_LOGIN.*User with DB ID'): - self.client.getUserByID(40000711) - -# def test_setTestMode(self): -# response = self.client.setTestMode(True) -# self.assertTrue(response) -# response = self.client.setTestMode(False) -# self.assertTrue(response) - - def test_deleteExecution_unknownKey(self): - try: - response = self.client.deleteExecution(40000711) - # case: TL configuration allows deletion of executions - # response returns Success, even if executionID is unkown - self.assertEqual([{'status': True, 'message': 'Success!', 'id': 40000711, - 'operation': 'deleteExecution'}], response) - except TLResponseError as tl_err: - # case: TL configuration does not allow deletion of executions - # Expects: 232: Configuration does not allow delete executions - self.assertEqual(232, tl_err.code) - - def test_setTestCaseExecutionType_unknownID(self): - with self.assertRaisesRegex(TLResponseError, '7000.*40000712'): - self.client.setTestCaseExecutionType('N-40000711', 1, 40000712, 1) - - def test_assignRequirements_unknownID(self): - with self.assertRaisesRegex(TLResponseError, '7000.*40000712'): - self.client.assignRequirements('N-40000711', 40000712, - [{'req_spec' : 40000713, 'requirements' : [40000714, 40000717]}, - {'req_spec' : 4723, 'requirements' : [4725]}]) - - def test_getExecCountersByBuild_unknownID(self): - with self.assertRaisesRegex(TLResponseError, '3000.*40000711'): - self.client.getExecCountersByBuild(40000711) - - def test_getTestCaseCustomFieldExecutionValue_unknownID(self): - with self.assertRaisesRegex(TLResponseError, '236.*version/executionid'): - self.client.getTestCaseCustomFieldExecutionValue( - 'cf_full', '40000711', 1, '715', '40000713') - - def test_getTestCaseCustomFieldTestPlanDesignValue_unknownID(self): - with self.assertRaisesRegex(TLResponseError, '7000.*40000711'): - self.client.getTestCaseCustomFieldTestPlanDesignValue( - 'cf_full', '40000711', 1, '40000713', '615') - - def test_updateTestCaseCustomFieldDesignValue_unknownID(self): - with self.assertRaisesRegex(TLResponseError, '7000.*40000711'): - self.client.updateTestCaseCustomFieldDesignValue( - 'TC-40000712', 1, 40000711, {'cf_field1' : 'value1', - 'cf_field2' : 'value2'}) - - def test_getTestSuiteCustomFieldDesignValue_unknownID(self): - with self.assertRaisesRegex(TLResponseError, '7000.*40000711'): - self.client.getTestSuiteCustomFieldDesignValue( - 'cf_full', 40000711, 40000713) - - def test_getTestPlanCustomFieldDesignValue_unknownID(self): - with self.assertRaisesRegex(TLResponseError, '7000.*40000711'): - self.client.getTestPlanCustomFieldDesignValue( - 'cf_full', 40000711, 40000712) - - def test_getReqSpecCustomFieldDesignValue_unknownID(self): - with self.assertRaisesRegex(TLResponseError, '7000.*40000711'): - self.client.getReqSpecCustomFieldDesignValue( - 'cf_full', 40000711, 4732) - - def test_getRequirementCustomFieldDesignValue_unknownID(self): - with self.assertRaisesRegex(TLResponseError, '7000.*40000711'): - self.client.getRequirementCustomFieldDesignValue( - 'cf_full', 40000711, 4734) - - def test_assignTestCaseExecutionTask_unknownID(self): - with self.assertRaisesRegex(TLResponseError, '3000.*40000711'): - self.client.assignTestCaseExecutionTask('username', 40000711, 'TC-40000712', - buildname='build 40000713', - platformname='platform 40000714') - - def test_getTestCaseBugs_unknownID(self): - with self.assertRaisesRegex(TLResponseError, '3000.*40000711'): - self.client.getTestCaseBugs(40000711, testcaseexternalid='TC-40000712', - buildname='build 40000713', - platformname='platform 40000714') - - def test_getTestCaseAssignedTester_unknownID(self): - with self.assertRaisesRegex(TLResponseError, '3000.*40000711'): - self.client.getTestCaseAssignedTester(40000711, 'TC-40000712', - buildname='build 40000713', - platformname='platform 40000714') - - def test_unassignTestCaseExecutionTask_unknownID(self): - with self.assertRaisesRegex(TLResponseError, '3000.*40000711'): - self.client.unassignTestCaseExecutionTask(40000711, 'TC-40000712', - buildname='build 40000713', - platformname='platform 40000714', - user='username',action='unassignOne') - - def test_getProjectKeywords_unknownID(self): - with self.assertRaisesRegex(TLResponseError, '7000.*40000711'): - self.client.getProjectKeywords(40000711) - - def test_getTestCaseKeywords_unknownID(self): - with self.assertRaisesRegex(TLResponseError, '5040.*40000712'): - self.client.getTestCaseKeywords(testcaseid=40000712) - - def test_getTestCaseKeywords_unknownID_set(self): - with self.assertRaisesRegex(TLResponseError, '5040.*40000712'): - self.client.getTestCaseKeywords(testcaseid=[40000712, 40000713]) - - def test_getTestCaseKeywords_unknownID_external_single(self): - with self.assertRaisesRegex(TLResponseError, '5040.*TC-40000712'): - self.client.getTestCaseKeywords(testcaseexternalid='TC-40000712') - - def test_getTestCaseKeywords_unknownID_external_set(self): - with self.assertRaisesRegex(TLResponseError, '5040.*TC-40000712'): - self.client.getTestCaseKeywords(testcaseexternalid=['TC-40000712', 'TC-40000713']) - - def test_deleteTestPlan_unknownID(self): - with self.assertRaisesRegex(TLResponseError, '3000.*40000711'): - self.client.deleteTestPlan(40000711) - - def test_addTestCaseKeywords_unknownID(self): - with self.assertRaisesRegex(TLResponseError, '5040.*TC-40000712'): - self.client.addTestCaseKeywords({'TC-40000712' : - ['KeyWord01', 'KeyWord03']}) - - def test_removeTestCaseKeywords_unknownID(self): - with self.assertRaisesRegex(TLResponseError, '5040.*TC-40000712'): - self.client.removeTestCaseKeywords({'TC-40000712' : ['KeyWord01']}) - - def test_deleteTestProject_unknownID(self): - with self.assertRaisesRegex(TLResponseError, '7013.*TProjectPrefix'): - self.client.deleteTestProject('TProjectPrefix') - - def test_createTestPlan_projectname_optArg_unknownID(self): - with self.assertRaisesRegex(TLResponseError, '7011.*40000712'): - self.client.createTestPlan('plan 40000711', - testprojectname='project 40000712') - - def test_createTestPlan_prefix_unknownID(self): - with self.assertRaisesRegex(TLResponseError, 'NO.*TProjectPrefix'): - self.client.createTestPlan('plan 40000713', - prefix='TProjectPrefix') - - def test_updateTestSuiteCustomFieldDesignValue_unknownID(self): - with self.assertRaisesRegex(TLResponseError, '7000.*40000712'): - self.client.updateTestSuiteCustomFieldDesignValue( - '40000712 TP-ID', '40000711 TS-ID', - {'cf_tc_ex_string' : 'a custom exec value', - 'cf_tc_ex_numeric' : 111} ) - - -if __name__ == "__main__": - #import sys;sys.argv = ['', 'Test.testName'] - unittest.main() \ No newline at end of file diff --git a/test/utest-online/testlinkapi_online_test.py b/test/utest-online/testlinkapi_online_test.py deleted file mode 100644 index c77d335..0000000 --- a/test/utest-online/testlinkapi_online_test.py +++ /dev/null @@ -1,433 +0,0 @@ -#! /usr/bin/python -# -*- coding: UTF-8 -*- - -# Copyright 2012-2015 Luiko Czub, TestLink-API-Python-client developers -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# ------------------------------------------------------------------------ - -# this test requires an online TestLink Server, which connection parameters -# are defined in environment variables -# TESTLINK_API_PYTHON_DEVKEY and TESTLINK_API_PYTHON_DEVKEY -# -# works with the example project NEW_PROJECT_API (see TestLinkExample.py) -# FIME LC 29.10.29: test does not really interacts with test link -# only negative test with none existing IDs implemented -# ok to check every implemented server call one time but not -# to cover all possible responses or argument combinations - -import sys -import os.path - -if sys.version_info[0] == 2 and sys.version_info[1] == 6: - # py26 needs backport unittest2 - import unittest2 as unittest -else: - import unittest - -if sys.version_info[0] == 2 and sys.version_info[1] == 7: - # py27 and py31 assertRaisesRegexp was renamed in py32 to assertRaisesRegex - unittest.TestCase.assertRaisesRegex = unittest.TestCase.assertRaisesRegexp - # py27 and py31 assertRegexpMatches was renamed in py32 to assertRegex - unittest.TestCase.assertRegex = unittest.TestCase.assertRegexpMatches - - -from testlink import TestlinkAPIClient, TestLinkHelper -from testlink.testlinkerrors import TLResponseError - -# example text file attachment = this python file -# why not using os.path.realpath(__file__) -# -> cause __file__ could be compiled python file *.pyc, if the test run is -# repeated without changing the test code -ATTACHMENT_EXAMPLE_TEXT= os.path.join(os.path.dirname(__file__), - 'testlinkapi_online_test.py') - -class TestLinkAPIOnlineTestCase(unittest.TestCase): - """ TestCases for TestlinkAPIClient - interacts with a TestLink Server. - works with the example project NEW_PROJECT_API (see TestLinkExample.py) - """ - - def setUp(self): - self.client = TestLinkHelper().connect(TestlinkAPIClient) - - -# def tearDown(self): -# pass - - def test_checkDevKey(self): - response = self.client.checkDevKey() - self.assertEqual(True, response) - - def test_about(self): - response = self.client.about() - self.assertIn('Testlink API', response) - - def test_ping(self): - response = self.client.ping() - self.assertEqual('Hello!', response) - - def test_echo(self): - response = self.client.echo('Yellow Submarine') - self.assertEqual('You said: Yellow Submarine', response) - - def test_doesUserExist_unknownID(self): - with self.assertRaisesRegex(TLResponseError, '10000.*Big Bird'): - self.client.doesUserExist('Big Bird') - - def test_getBuildsForTestPlan_unknownID(self): - with self.assertRaisesRegex(TLResponseError, '3000.*40000711'): - self.client.getBuildsForTestPlan(40000711) - - def test_getFirstLevelTestSuitesForTestProject_unknownID(self): - with self.assertRaisesRegex(TLResponseError, '7000.*40000711'): - self.client.getFirstLevelTestSuitesForTestProject(40000711) - - def test_getFullPath_unknownID(self): - with self.assertRaisesRegex(TLResponseError, 'getFullPath.*234'): - self.client.getFullPath('40000711') - - def test_getLastExecutionResult_unknownID(self): - with self.assertRaisesRegex(TLResponseError, '3000.*40000711'): - self.client.getLastExecutionResult(40000711, 40000712) - - def test_getLatestBuildForTestPlan_unknownID(self): - with self.assertRaisesRegex(TLResponseError, '3000.*40000711'): - self.client.getLatestBuildForTestPlan(40000711) - - def test_getProjects(self): - response = self.client.getProjects() - self.assertIsNotNone(response) - - def test_getProjectTestPlans_unknownID(self): - with self.assertRaisesRegex(TLResponseError, '7000.*40000711'): - self.client.getProjectTestPlans(40000711) - - def test_getProjectPlatforms_unknownID(self): - with self.assertRaisesRegex(TLResponseError, '7000.*40000711'): - self.client.getProjectPlatforms(40000711) - - def test_getTestCase_unknownID(self): - with self.assertRaisesRegex(TLResponseError, '5000.*40000711'): - self.client.getTestCase(40000711) - - def test_getTestCase_unknownExternalID(self): - with self.assertRaisesRegex(TLResponseError, '5040.*N-2'): - self.client.getTestCase(testcaseexternalid='N-2') - - def test_getTestCaseAttachments_unknownID(self): - with self.assertRaisesRegex(TLResponseError, '5000.*40000711'): - self.client.getTestCaseAttachments(40000711) - - def test_getTestCaseCustomFieldDesignValue_unknownID(self): - with self.assertRaisesRegex(TLResponseError, '7000.*40000711'): - self.client.getTestCaseCustomFieldDesignValue( - 'TC-40000712', 1, 40000711, 'a_field', 'a_detail') - - def test_getTestCaseIDByName_unknownID(self): - with self.assertRaisesRegex(TLResponseError, '5030.*Cannot find'): - self.client.getTestCaseIDByName('Big Bird') - - def test_getTestCasesForTestPlan_unknownID(self): - with self.assertRaisesRegex(TLResponseError, '3000.*40000711'): - self.client.getTestCasesForTestPlan(40000711) - - def test_getTestCasesForTestSuite_unknownID(self): - with self.assertRaisesRegex(TLResponseError, '8000.*40000711'): - self.client.getTestCasesForTestSuite(40000711, 2, 'a_detail') - - def test_getTestPlanByName_unknownID(self): - with self.assertRaisesRegex(TLResponseError, '7011.*40000711'): - self.client.getTestPlanByName('project 40000711', 'plan 40000712') - - def test_getTestPlanPlatforms_unknownID(self): - with self.assertRaisesRegex(TLResponseError, '3000.*40000711'): - self.client.getTestPlanPlatforms(40000711) - - def test_getTestProjectByName_unknownID(self): - with self.assertRaisesRegex(TLResponseError, '7011.*40000711'): - self.client.getTestProjectByName('project 40000711') - - def test_getTestSuiteByID_unknownID(self): - with self.assertRaisesRegex(TLResponseError, '8000.*40000711'): - self.client.getTestSuiteByID(40000711) - - def test_getTestSuitesForTestPlan_unknownID(self): - with self.assertRaisesRegex(TLResponseError, '3000.*40000711'): - self.client.getTestSuitesForTestPlan(40000711) - - def test_getTestSuitesForTestSuite_unknownID(self): - with self.assertRaisesRegex(TLResponseError, '8000.*40000711'): - self.client.getTestSuitesForTestSuite(40000711) - - def test_getTotalsForTestPlan_unknownID(self): - with self.assertRaisesRegex(TLResponseError, '3000.*40000711'): - self.client.getTotalsForTestPlan(40000711) - - def test_createTestProject_unknownID(self): - with self.assertRaisesRegex(TLResponseError, '7001.*Empty name'): - self.client.createTestProject('', 'P40000711') - - def test_createBuild_unknownID(self): - with self.assertRaisesRegex(TLResponseError, '3000.*40000711'): - self.client.createBuild(40000711, 'Build 40000712', 'note 40000713') - - def test_createTestPlan_projectname_posArg_unknownID(self): - with self.assertRaisesRegex(TLResponseError, '7011.*40000712'): - self.client.createTestPlan('plan 40000711', 'project 40000712') - - def test_createTestSuite_unknownID(self): - with self.assertRaisesRegex(TLResponseError, '7000.*40000711'): - self.client.createTestSuite( 40000711, 'suite 40000712', 'detail 40000713') - - def test_createTestCase_unknownID(self): - with self.assertRaisesRegex(TLResponseError, '7000.*40000713'): - self.client.createTestCase('case 40000711', 40000712, 40000713, - 'Big Bird', 'summary 40000714') - - def test_reportTCResult_unknownID(self): - with self.assertRaisesRegex(TLResponseError, '5000.*40000711'): - self.client.reportTCResult(40000711, 40000712, 'build 40000713', 'p', - 'note 40000714') - - def test_uploadExecutionAttachment_unknownID(self): - attachemantFile = open(os.path.realpath(__file__), 'r') - with self.assertRaisesRegex(TLResponseError, '6004.*40000712'): - self.client.uploadExecutionAttachment(attachemantFile, 40000712, - 'title 40000713', 'descr. 40000714') - - def test_getProjectIDByName_unknownID(self): - response = self.client.getProjectIDByName('project 40000711') - self.assertEqual(-1, response) - - def test_createPlatform_unknownID(self): - with self.assertRaisesRegex(TLResponseError, '7011.*40000711'): - self.client.createPlatform('Project 40000711', 'Platform 40000712', - notes='note 40000713') - - def test_addTestCaseToTestPlan_unknownID(self): - with self.assertRaisesRegex(TLResponseError, '7000.*40000711'): - self.client.addTestCaseToTestPlan(40000711, 40000712, 'N-40000713', 1) - - def test_updateTestCase_unknownID(self): - with self.assertRaisesRegex(TLResponseError, '5040.*N-40000711'): - self.client.updateTestCase('N-40000711', version=1) - - def test_createTestCaseSteps_unknownID(self): - steps = [{'actions' : "Step action 6 -b added by updateTestCase" , - 'expected_results' : "Step result 6 - b added", - 'step_number' : 6, 'execution_type' : 1}] - with self.assertRaisesRegex(TLResponseError, '5040.*N-40000711'): - self.client.createTestCaseSteps('update', steps, - testcaseexternalid='N-40000711', version=1) - - def test_deleteTestCaseSteps_unknownID(self): - steps = [2,8] - with self.assertRaisesRegex(TLResponseError, '5040.*N-40000711'): - self.client.deleteTestCaseSteps('N-40000711', steps, version=1) - - def test_uploadRequirementSpecificationAttachment_unknownID(self): - attachemantFile = open(ATTACHMENT_EXAMPLE_TEXT, 'r') - with self.assertRaisesRegex(TLResponseError, '6004.*40000712'): - self.client.uploadRequirementSpecificationAttachment(attachemantFile, 40000712, - title='title 40000713', description='descr. 40000714') - - def test_uploadRequirementAttachment_unknownID(self): - attachemantFile = open(ATTACHMENT_EXAMPLE_TEXT, 'r') - with self.assertRaisesRegex(TLResponseError, '6004.*40000712'): - self.client.uploadRequirementAttachment(attachemantFile, 40000712, - title='title 40000713', description='descr. 40000714') - - def test_uploadTestProjectAttachment_unknownID(self): - attachemantFile = open(ATTACHMENT_EXAMPLE_TEXT, 'r') - with self.assertRaisesRegex(TLResponseError, '7000.*40000712'): - self.client.uploadTestProjectAttachment(attachemantFile, 40000712, - title='title 40000713', description='descr. 40000714') - - def test_uploadTestSuiteAttachment_unknownID(self): - attachemantFile = open(ATTACHMENT_EXAMPLE_TEXT, 'r') - with self.assertRaisesRegex(TLResponseError, '8000.*40000712'): - self.client.uploadTestSuiteAttachment(attachemantFile, 40000712, - title='title 40000713', description='descr. 40000714') - - def test_uploadTestCaseAttachment_unknownID(self): - attachemantFile = open(ATTACHMENT_EXAMPLE_TEXT, 'r') - with self.assertRaisesRegex(TLResponseError, '5000.*testcaseid'): - self.client.uploadTestCaseAttachment(attachemantFile, 40000712, - title='title 40000713', description='descr. 40000714') - - def test_uploadAttachment_unknownID(self): - attachemantFile = open(ATTACHMENT_EXAMPLE_TEXT, 'r') - with self.assertRaisesRegex(TLResponseError, '6004.*Invalid Foreign Key ID'): - self.client.uploadAttachment(attachemantFile, '0000', 'nodes_hierarchy', - title='title 40000713', description='descr. 40000714') - - def test_testLinkVersion(self): - response = self.client.testLinkVersion() - self.assertRegex(response, '\d*\.\d*\.\d*') - - def test_getUserByLogin_unknownKey(self): - with self.assertRaisesRegex(TLResponseError, '10000.*User Login'): - self.client.getUserByLogin(user='unknownUser') - -# def test_setTestMode(self): -# response = self.client.setTestMode(True) -# self.assertTrue(response) -# response = self.client.setTestMode(False) -# self.assertTrue(response) - - def test_deleteExecution_unknownKey(self): - try: - response = self.client.deleteExecution(40000711) - # case: TL configuration allows deletion of executions - # response returns Success, even if executionID is unkown - self.assertEqual([{'status': True, 'message': 'Success!', 'id': 40000711, - 'operation': 'deleteExecution'}], response) - except TLResponseError as tl_err: - # case: TL configuration does not allow deletion of executions - # Expects: 232: Configuration does not allow delete executions - self.assertEqual(232, tl_err.code) - - def test_setTestCaseExecutionType_unknownID(self): - with self.assertRaisesRegex(TLResponseError, '7000.*40000712'): - self.client.setTestCaseExecutionType('N-40000711', 1, 40000712, 1) - - def test_assignRequirements_unknownID(self): - with self.assertRaisesRegex(TLResponseError, '7000.*40000712'): - self.client.assignRequirements('N-40000711', 40000712, - [{'req_spec' : 40000713, 'requirements' : [40000714, 40000717]}, - {'req_spec' : 4723, 'requirements' : [4725]}]) - - def test_getExecCountersByBuild_unknownID(self): - with self.assertRaisesRegex(TLResponseError, '3000.*40000711'): - self.client.getExecCountersByBuild(40000711) - - def test_getTestCaseCustomFieldExecutionValue_unknownID(self): - with self.assertRaisesRegex(TLResponseError, '236.*version/executionid'): - self.client.getTestCaseCustomFieldExecutionValue( - 'cf_full', '40000711', 1, '715', '40000713') - - def test_getTestCaseCustomFieldTestPlanDesignValue_unknownID(self): - with self.assertRaisesRegex(TLResponseError, '7000.*40000711'): - self.client.getTestCaseCustomFieldTestPlanDesignValue( - 'cf_full', '40000711', 1, '40000713', '615') - - def test_updateTestCaseCustomFieldDesignValue_unknownID(self): - with self.assertRaisesRegex(TLResponseError, '7000.*40000711'): - self.client.updateTestCaseCustomFieldDesignValue( - 'TC-40000712', 1, 40000711, {'cf_field1' : 'value1', - 'cf_field2' : 'value2'}) - - def test_getTestSuiteCustomFieldDesignValue_unknownID(self): - with self.assertRaisesRegex(TLResponseError, '7000.*40000711'): - self.client.getTestSuiteCustomFieldDesignValue( - 'cf_full', 40000711, 40000713) - - def test_getTestPlanCustomFieldDesignValue_unknownID(self): - with self.assertRaisesRegex(TLResponseError, '7000.*40000711'): - self.client.getTestPlanCustomFieldDesignValue( - 'cf_full', 40000711, 40000712) - - def test_getReqSpecCustomFieldDesignValue_unknownID(self): - with self.assertRaisesRegex(TLResponseError, '7000.*40000711'): - self.client.getReqSpecCustomFieldDesignValue( - 'cf_full', 40000711, 4732) - - def test_getRequirementCustomFieldDesignValue_unknownID(self): - with self.assertRaisesRegex(TLResponseError, '7000.*40000711'): - self.client.getRequirementCustomFieldDesignValue( - 'cf_full', 40000711, 4734) - - def test_assignTestCaseExecutionTask_unknownID(self): - with self.assertRaisesRegex(TLResponseError, '3000.*40000711'): - self.client.assignTestCaseExecutionTask('username', 40000711, 'TC-40000712', - buildname='build 40000713', - platformname='platform 40000714') - - def test_getTestCaseBugs_unknownID(self): - with self.assertRaisesRegex(TLResponseError, '3000.*40000711'): - self.client.getTestCaseBugs(40000711, testcaseexternalid='TC-40000712', - buildname='build 40000713', - platformname='platform 40000714') - - def test_getTestCaseAssignedTester_unknownID(self): - with self.assertRaisesRegex(TLResponseError, '3000.*40000711'): - self.client.getTestCaseAssignedTester(40000711, 'TC-40000712', - buildname='build 40000713', - platformname='platform 40000714') - - def test_unassignTestCaseExecutionTask_unknownID(self): - with self.assertRaisesRegex(TLResponseError, '3000.*40000711'): - self.client.unassignTestCaseExecutionTask(40000711, 'TC-40000712', - buildname='build 40000713', - platformname='platform 40000714', - user='username',action='unassignOne') - - def test_getProjectKeywords_unknownID(self): - with self.assertRaisesRegex(TLResponseError, '7000.*40000711'): - self.client.getProjectKeywords(40000711) - - def test_getTestCaseKeywords_unknownID(self): - with self.assertRaisesRegex(TLResponseError, '5040.*40000712'): - self.client.getTestCaseKeywords(testcaseid=40000712) - - def test_getTestCaseKeywords_unknownID_set(self): - with self.assertRaisesRegex(TLResponseError, '5040.*40000712'): - self.client.getTestCaseKeywords(testcaseid=[40000712, 40000713]) - - def test_getTestCaseKeywords_unknownID_external_single(self): - with self.assertRaisesRegex(TLResponseError, '5040.*TC-40000712'): - self.client.getTestCaseKeywords(testcaseexternalid='TC-40000712') - - def test_getTestCaseKeywords_unknownID_external_set(self): - with self.assertRaisesRegex(TLResponseError, '5040.*TC-40000712'): - self.client.getTestCaseKeywords(testcaseexternalid=['TC-40000712', 'TC-40000713']) - - def test_deleteTestPlan_unknownID(self): - with self.assertRaisesRegex(TLResponseError, '3000.*40000711'): - self.client.deleteTestPlan(40000711) - - def test_addTestCaseKeywords_unknownID(self): - with self.assertRaisesRegex(TLResponseError, '5040.*TC-40000712'): - self.client.addTestCaseKeywords({'TC-40000712' : - ['KeyWord01', 'KeyWord03']}) - - def test_removeTestCaseKeywords_unknownID(self): - with self.assertRaisesRegex(TLResponseError, '5040.*TC-40000712'): - self.client.removeTestCaseKeywords({'TC-40000712' : ['KeyWord01']}) - - def test_deleteTestProject_unknownID(self): - with self.assertRaisesRegex(TLResponseError, '7013.*TProjectPrefix'): - self.client.deleteTestProject('TProjectPrefix') - - def test_createTestPlan_projectname_optArg_unknownID(self): - with self.assertRaisesRegex(TLResponseError, '7011.*40000712'): - self.client.createTestPlan('plan 40000711', - testprojectname='project 40000712') - - def test_createTestPlan_prefix_unknownID(self): - with self.assertRaisesRegex(TLResponseError, 'NO.*TProjectPrefix'): - self.client.createTestPlan('plan 40000713', - prefix='TProjectPrefix') - - def test_updateTestSuiteCustomFieldDesignValue_unknownID(self): - with self.assertRaisesRegex(TLResponseError, '7000.*40000712'): - self.client.updateTestSuiteCustomFieldDesignValue( - '40000712 TP-ID', '40000711 TS-ID', - {'cf_tc_ex_string' : 'a custom exec value', - 'cf_tc_ex_numeric' : 111} ) - - -if __name__ == "__main__": - #import sys;sys.argv = ['', 'Test.testName'] - unittest.main() \ No newline at end of file diff --git a/tox.ini b/tox.ini index 3f73e85..e8de136 100644 --- a/tox.ini +++ b/tox.ini @@ -4,11 +4,9 @@ # TESTLINK_API_PYTHON_DEVKEY and TESTLINK_API_PYTHON_SERVER_URL # with your server settings [tox] -envlist = py26,py27,py33,py34 +envlist = py27,py36,py37 [testenv] deps=pytest - py26: unittest2 - py26: argparse setenv= TESTLINK_API_PYTHON_DEVKEY={env:TESTLINK_API_PYTHON_DEVKEY:3ec96581bb3d8a34cd28ce338b641063} TESTLINK_API_PYTHON_SERVER_URL={env:TESTLINK_API_PYTHON_SERVER_URL:http://demo.testlink.org/latest/lib/api/xmlrpc/v1/xmlrpc.php}