Skip to content

Commit

Permalink
Added Zephyr Squad Server support
Browse files Browse the repository at this point in the history
This commit introduces initial support for the Zephyr Squad (server)
variant. Only a number of the available Zephyr API calls are introduced
by this commit.

All testing for this commit was done on a self-hosted Zephyr Squad instance.
  • Loading branch information
crapitea committed Nov 2, 2023
1 parent 4125a73 commit 9467feb
Show file tree
Hide file tree
Showing 37 changed files with 1,599 additions and 91 deletions.
6 changes: 3 additions & 3 deletions .pylintrc
Original file line number Diff line number Diff line change
Expand Up @@ -390,8 +390,8 @@ preferred-modules=
[EXCEPTIONS]

# Exceptions that will emit a warning when caught.
overgeneral-exceptions=BaseException,
Exception
overgeneral-exceptions=builtins.BaseException,
builtins.Exception


[REFACTORING]
Expand Down Expand Up @@ -453,7 +453,7 @@ max-locals=15
max-parents=7

# Maximum number of public methods for a class (see R0904).
max-public-methods=20
max-public-methods=30

# Maximum number of return / yield for function / method body.
max-returns=6
Expand Down
67 changes: 57 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,31 +4,33 @@
![PyPI - Python Version](https://img.shields.io/pypi/pyversions/zephyr-python-api)
![PyPI](https://img.shields.io/pypi/v/zephyr-python-api)
![PyPI - License](https://img.shields.io/pypi/l/zephyr-python-api)
### Project description
This is a set of wrappers for Zephyr Scale (TM4J) REST API. This means you can interact with Zephyr Scale without GUI, access it with python code and create automation scripts for your every day interactions.
## Project description
This is a set of wrappers for both Zephyr Scale and Zephyr Squad (TM4J) REST APIs. This means you can interact with Zephyr without GUI, access it with python code and create automation scripts for your every day interactions.

To be done:
* More usage examples
* Tests, tests and tests for gods of testing
* Convenient docs
* Implementing higher level wrappers representing Test Case, Test Cycle, etc.

### Installation
## Installation

```
pip install zephyr-python-api
```

### Example usage
## Example usage

Zephyr Cloud auth:
### Zephyr Scale

Zephyr Scale Cloud auth:
```python
from zephyr import ZephyrScale

zscale = ZephyrScale(token=<your_token>)
```

Zephyr Server (TM4J) auth:
Zephyr Scale Server (TM4J) auth:
```python
from zephyr import ZephyrScale

Expand Down Expand Up @@ -58,17 +60,62 @@ test_case = zapi.test_cases.get_test_case("<test_case_id>")
creation_result = zapi.test_cases.create_test_case("<project_key>", "test_case_name")
```

### Troubleshooting
### Zephyr Squad

Zephyr Squad Server (TM4J) auth:
```python
from zephyr import ZephyrSquad

# Auth can be made with Jira token
auth = {"token": "<your_jira_token>"}

# or with login and password (suggest using get_pass)
auth = {"username": "<your_login>", "password": "<your_password>"}

# or even session cookie dict
auth = {"cookies": "<session_cookie_dict>"}

zsquad = ZephyrSquad(base_url=base_url, **auth)
```

Then it is possible to interact with api wrappers:
```python
# Obtain a project's information
project_info = zsquad.actions.project.get_project_info("<project_key>")

# Obtain a project's versions/releases
project_versions = zsquad.api.util_resource.get_all_versions("<project_id>")

# Get a single test case by its id
test_case = zsquad.actions.test_cases.get_test_case("<case_key>", fields="id")

# Create a new test case for a project
data = {
"fields": {
"assignee": {
"name": "<jira_username>"
},
"description": "<case_description>"
}
}
creation_result = zsquad.actions.test_cases.create_test_case(projectId="<project_id>", summary="<case_summary>", data=data)
```

## Troubleshooting

For troubleshooting see [TROUBLESHOOTING.md](TROUBLESHOOTING.md)


### License
## License

This library is licensed under the Apache 2.0 License.

### Links
## Links

[Zephyr Scale Cloud API docs](https://support.smartbear.com/zephyr-scale-cloud/api-docs/)

[Zephyr Scale Server API docs](https://support.smartbear.com/zephyr-scale-server/api-docs/v1/)
[Zephyr Scale Server API docs](https://support.smartbear.com/zephyr-scale-server/api-docs/v1/)

[Zephyr Squad Server API docs](https://zephyrsquadserver.docs.apiary.io/)

[Zephyr Squad Server How to API docs](https://support.smartbear.com/zephyr-squad-server/docs/api/index.html)
File renamed without changes.
133 changes: 133 additions & 0 deletions examples/squad-server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
"""
Usage examples of Zephyr Squad Server API wrappers.
"""
import logging

from zephyr import ZephyrSquad

# Enable logging with level Debug for more verbosity
logging.basicConfig(level=logging.DEBUG)


# Specify your Jira context to operate with:
base_url = "https://jira.hosted.com/"

# Use the Jira certificate for TLS connections
session_params = {
"verify": "<path-to-certificate>"
}

# Create an instance of Zephyr Squad
zsquad = ZephyrSquad(
base_url=base_url,
token="<token>",
session_attrs=session_params
)

# Now we can start playing with the Zephyr API!

# Obtain a project's information
project_info = zsquad.actions.project.get_project_info("<project_key>")

# Obtain a project's versions/releases
project_versions = zsquad.api.util_resource.get_all_versions("<project_id>")

# Get the data of a testcase
test_case = zsquad.actions.test_cases.get_test_case("<case_key>", fields="id")

# Get the test steps from a testcase
test_steps = zsquad.api.teststep_resource.get_list_of_teststeps("<issue_id>")

# Get the information about a test cycle
test_cycle = zsquad.api.cycle_resource.get_cycle_information(cycle_id="<cycle_id>")

# Get the list of all test cycles for a specific release
test_cycles = zsquad.api.cycle_resource.get_list_of_cycle(project_id="<project_id>", versionId="<version_id>")

# Get all folders from a test cycle
test_cycle_folders = zsquad.api.cycle_resource.get_the_list_of_folder_for_a_cycle(cycle_id="<cycle_id>", project_id="<project_id>", version_id="<version_id>")

# Get all test executions from a test case
test_executions = zsquad.api.traceability_resource.get_list_of_search_execution_by_test(test_id_or_key="<test_id_or_key>")

# Create a new test case for a project
data = {
"fields": {
"assignee": {
"name": "<jira_username>"
},
"description": "<case_description>"
}
}
ret_data = zsquad.actions.test_cases.create_test_case(project_id="<project_id>", summary="<case_summary>", data=data)

# Execute ZQL search query
demo_query = "project = '<project_id>' AND cycleName = '<cycle_name>'"
zql_search_res = zsquad.api.execution_search_resource.execute_search_to_get_search_result(query=demo_query, maxRecords=200)

# Create a new test cycle for a project based on an existing test case
data = {
"clonedCycleId": "<cycle_id>",
"description": "<cycle_description>",
"build": "",
"startDate": "29/Nov/22",
"endDate": "4/Dec/22",
"environment": ""
}
ret_data = zsquad.api.cycle_resource.create_new_cycle(project_id="<project_id>", version_id="<version_id>", name="<cycle_name>", data=data)

# Create a new test folder for a test cycle
data = {
"cycleId": 1508, # it will be rewritten by the function
"name": "<folder_name>",
"description": "<folder_description>",
"projectId": 10600, # it will be rewritten by the function
"versionId": -1, # it will be rewritten by the function
"clonedFolderId": -1
}
ret_data = zsquad.api.folder_resource.create_folder_under_cycle(project_id="<project_id>", version_id="<version_id>", cycle_id="<cycle_id>", data=data)

# Add a new test case for a test cycle
data = {
"issues":["<case_key>"],
}
ret_data = zsquad.api.execution_resource.add_test_to_cycle(project_id="<project_id>", cycle_id="<cycle_id>", method="1", data=data)

# Obtain the execution details
exec_details = zsquad.api.execution_resource.get_execution_information(execution_id="<execution_id>")

# Obtain the execution steps from an execution
exec_steps = zsquad.api.step_result_resource.get_list_of_step_result(execution_id="<execution_id>")

# Update the status of an execution step
step_status = zsquad.api.step_result_resource.update_step_result_information(step_result_id="<execution_step_id>", status=2)

# Update the execution status
exec_status = zsquad.api.execution_resource.update_execution_details(execution_id="<execution_id>", status=2)

# Update a folder name and description
data = {
"description": "<new_folder_decription>"
}
ret_data = zsquad.api.folder_resource.update_folder_information(project_id="<project_id>", version_id="<version_id>", cycle_id="<cycle_id>", folder_id="<folder_id>", name="<new_folder_name>")

# Delete 3 test executions
delete_status = zsquad.api.execution_resource.delete_bulk_execution(execution_id=["<exec_id_1>", "<exec_id_2>", "<exec_id_3>"])

# Show the progress of a job
job_status = zsquad.api.execution_resource.get_job_progress_status(job_progress_token="<job_progress_token>")

# Get a test step's detailed information
test_step = zsquad.api.teststep_resource.get_teststep_information(test_step_id="<test_step_id>", issue_id="<issue_id>")

# Add a attachment (for a execution result: entityId=executionId and entityType='Execution')
attach = zsquad.api.attachment_resource.add_attachment_into_entity(file_path="<file_path>", entity_id="<entity_id>", entity_type='<entity_type>')

# Add a assignee to a execution result
add_assignee = zsquad.api.execution_resource.add_assignee_to_execution(execution_id="<exec_id>", assignee="<username>")

# Create a test execution result in a test cycle
data = {
"folderId": "<folder_id>"
}
ret_data = zsquad.api.execution_resource.create_new_execution(project_id="<project_id>", cycle_id="<cycle_id>", version_id="<version_id>", issue_id="<issue_id>", data=data)
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,4 @@ install_requires =

[options.packages.find]
exclude =
tests*
tests*
2 changes: 1 addition & 1 deletion tests/unit/test_scale.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from zephyr.scale.scale import DEFAULT_BASE_URL, ZephyrScale


ZSESSION_PATH = "zephyr.scale.scale.ZephyrSession"
ZSESSION_PATH = "zephyr.scale.scale.ZephyrScaleSession"
CLOUD_API_WRAP_PATH = "zephyr.scale.scale.CloudApiWrapper"
SERVER_API_WRAP_PATH = "zephyr.scale.scale.ServerApiWrapper"

Expand Down
20 changes: 10 additions & 10 deletions tests/unit/test_zephyr_session.py
Original file line number Diff line number Diff line change
@@ -1,33 +1,33 @@
import pytest
from requests import Session

from zephyr.scale.scale import DEFAULT_BASE_URL, ZephyrSession
from zephyr.scale.zephyr_session import INIT_SESSION_MSG, InvalidAuthData
from zephyr.scale.scale import DEFAULT_BASE_URL, ZephyrScaleSession
from zephyr.common.zephyr_session import INIT_SESSION_MSG, InvalidAuthData

REQUESTS_SESSION_PATH = "requests.sessions.Session"
GETLOGGER_PATH = "logging.getLogger"
LOGGER_DEBUG_PATH = "logging.Logger.debug"


@pytest.mark.unit
class TestZephyrSession:
class TestZephyrScaleSession:
def test_creation(self, mocker):
"""Tests basic creation logic"""
logger_mock = mocker.patch(GETLOGGER_PATH)

zsession = ZephyrSession(DEFAULT_BASE_URL, token="token_test")
zsession = ZephyrScaleSession(DEFAULT_BASE_URL, token="token_test")

assert zsession.base_url == DEFAULT_BASE_URL, (f"Attribute base_url expected to be {DEFAULT_BASE_URL}, "
f"not {zsession.base_url}")
assert isinstance(zsession._session, Session)
logger_mock.assert_called_with("zephyr.scale.zephyr_session")
logger_mock.assert_called_with("zephyr.common.zephyr_session")

def test_token_auth(self, mocker):
"""Test token auth"""
token = "test_token"
logger_mock = mocker.patch(LOGGER_DEBUG_PATH)

zsession = ZephyrSession(DEFAULT_BASE_URL, token=token)
zsession = ZephyrScaleSession(DEFAULT_BASE_URL, token=token)

logger_mock.assert_called_with(INIT_SESSION_MSG.format("token"))
assert f"Bearer {token}" == zsession._session.headers.get("Authorization")
Expand All @@ -38,7 +38,7 @@ def test_credentials_auth(self, mocker):
password = "pwdtest"
logger_mock = mocker.patch(LOGGER_DEBUG_PATH)

zsession = ZephyrSession(DEFAULT_BASE_URL, username=username, password=password)
zsession = ZephyrScaleSession(DEFAULT_BASE_URL, username=username, password=password)

logger_mock.assert_called_with(INIT_SESSION_MSG.format("username and password"))
assert (username, password) == zsession._session.auth
Expand All @@ -48,7 +48,7 @@ def test_cookie_auth(self, mocker):
test_cookie = {"cookies": {"cookie.token": "cookie_test"}}
logger_mock = mocker.patch(LOGGER_DEBUG_PATH)

zsession = ZephyrSession(DEFAULT_BASE_URL, cookies=test_cookie)
zsession = ZephyrScaleSession(DEFAULT_BASE_URL, cookies=test_cookie)

logger_mock.assert_called_with(INIT_SESSION_MSG.format("cookies"))
assert test_cookie['cookies'] in zsession._session.cookies.values()
Expand All @@ -59,7 +59,7 @@ def test_cookie_auth(self, mocker):
def test_auth_exception(self, auth_data, exception):
"""Test exceptions on auth"""
with pytest.raises(exception):
ZephyrSession(DEFAULT_BASE_URL, **auth_data)
ZephyrScaleSession(DEFAULT_BASE_URL, **auth_data)

@pytest.mark.parametrize("creation_kwargs",
[{"token": "token_test",
Expand All @@ -69,7 +69,7 @@ def test_requests_session_attrs(self, creation_kwargs, mocker):
logger_mock = mocker.patch(LOGGER_DEBUG_PATH)
session_attrs = creation_kwargs.get('session_attrs')

zsession = ZephyrSession(DEFAULT_BASE_URL, **creation_kwargs)
zsession = ZephyrScaleSession(DEFAULT_BASE_URL, **creation_kwargs)

logger_mock.assert_called_with(
f"Modify requests session object with {session_attrs}")
Expand Down
1 change: 1 addition & 0 deletions zephyr/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
from zephyr.scale import API_V1, API_V2, ZephyrScale
from zephyr.squad import ZephyrSquad
from zephyr.utils.common import cookie_str_to_dict
Loading

0 comments on commit 9467feb

Please sign in to comment.