Skip to content

Commit

Permalink
feat: POC automation test
Browse files Browse the repository at this point in the history
JIRA: QA-23477
risk: nonprod
  • Loading branch information
Tubt committed Dec 27, 2024
1 parent a576d4f commit 6f4fa0f
Show file tree
Hide file tree
Showing 15 changed files with 456 additions and 1 deletion.
2 changes: 1 addition & 1 deletion .python-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
3.12.4
3.11.4
9 changes: 9 additions & 0 deletions gooddata-sdk/env.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# (C) 2024 GoodData Corporation
# env.py
import os

# Define environment variables
HOST = os.getenv("GOODDATA_HOST", "https://checklist.staging.stg11.panther.intgdc.com")
TOKEN = os.getenv("GOODDATA_TOKEN", "<your token>")
DATASOURCE_ID = os.getenv("DATASOURCE_ID", "your datasource")
WORKSPACE_ID = "your workspace"
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"id": "total_returns_per_month",
"title": "Total Returns per Month",
"visualizationType": "COLUMN",
"metrics": [
{
"id": "total_returns",
"type": "metric",
"title": "Total Returns"
}
],
"dimensionality": [
{
"id": "return_date.month",
"type": "attribute",
"title": "Return date - Month/Year"
}
],
"filters": [],
"suggestions": [
{
"query": "Switch to a line chart to better visualize the trend of total returns over the months.",
"label": "Line Chart for Trends"
},
{
"query": "Filter the data to show total returns for this year only.",
"label": "This Year's Returns"
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"id": "total_returns_per_month_current_year",
"title": "Total Returns Per Month - Current Year",
"visualizationType": "COLUMN",
"metrics": [
{
"id": "total_returns",
"type": "metric",
"title": "Total Returns"
}
],
"dimensionality": [
{
"id": "return_date.month",
"type": "attribute",
"title": "Return date - Month/Year"
}
],
"filters": [
{
"using": "return_date",
"granularity": "YEAR",
"_from": 0,
"to": 0
}
],
"suggestions": [
{
"query": "Switch to a line chart to better represent the trend of total returns over months.",
"label": "Show Line Chart Trend"
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"id": "total_customers_per_month_current_year",
"title": "Total Customers per Month - Current Year",
"visualizationType": "LINE",
"metrics": [
{
"id": "total_customers",
"type": "metric",
"title": "Total Customers"
}
],
"dimensionality": [
{
"id": "date.month",
"type": "attribute",
"title": "Date - Month/Year"
}
],
"filters": [
{
"using": "date",
"granularity": "YEAR",
"_from": 0,
"to": 0
}
],
"suggestions": [
{
"query": "Consider switching to a COLUMN chart for clearer comparisons of Total Customers per month.",
"label": "Switch to COLUMN chart"
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"id": "total_returns_per_month_current_year",
"title": "Total Returns Per Month - Current Year",
"visualizationType": "LINE",
"metrics": [
{
"id": "total_returns",
"type": "metric",
"title": "Total Returns"
}
],
"dimensionality": [
{
"id": "return_date.month",
"type": "attribute",
"title": "Return date - Month/Year"
}
],
"filters": [
{
"using": "return_date",
"granularity": "YEAR",
"_from": 0,
"to": 0
}
],
"suggestions": [
{
"query": "Switch to a line chart to better represent the trend of total returns over months.",
"label": "Show Line Chart Trend"
}
]
}
18 changes: 18 additions & 0 deletions gooddata-sdk/integration_tests/fixtures/ai_questions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
[
{
"question": "What is total returns per month? show as COLUMN chart",
"expected_objects_file": "column_total_returns_by_month.json"
},
{
"question": "Add filter by current year, show as COLUMN chart",
"expected_objects_file": "column_total_returns_by_month_filter_current_year.json"
},
{
"question": "Switch to LINE chart",
"expected_objects_file": "line_total_returns_by_month_filter_current_year.json"
},
{
"question": "Replace metric Total Customers instead of total returns",
"expected_objects_file": "line_total_customers_by_month_filter_current_year.json"
}
]
83 changes: 83 additions & 0 deletions gooddata-sdk/integration_tests/scripts/aiChat.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# (C) 2024 GoodData Corporation
import os
import sys

import gooddata_api_client
import pytest
from gooddata_api_client.api import smart_functions_api
from gooddata_api_client.model.chat_history_request import ChatHistoryRequest
from gooddata_api_client.model.chat_history_result import ChatHistoryResult
from gooddata_api_client.model.chat_request import ChatRequest
from utils import load_json, normalize_metrics

sys.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

from env import WORKSPACE_ID

EXPECTED_OBJECTS_DIR = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "data_response")
QUESTIONS_DIR = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "fixtures")

questions_list = load_json(os.path.join(QUESTIONS_DIR, "ai_questions.json"))


class gooddataAiChatApp:
def __init__(self, api_client, workspace_id):
self.api_instance = smart_functions_api.SmartFunctionsApi(api_client)
self.workspace_id = workspace_id

async def ask_question(self, question: str):
return self.api_instance.ai_chat(self.workspace_id, ChatRequest(question=question))

async def chat_history(self, interaction_id: int, feedback: str):
return self.api_instance.ai_chat_history(
self.workspace_id, ChatHistoryRequest(chat_history_interaction_id=interaction_id, user_feedback=feedback)
)


def validate_response(actual_response, expected_response):
actual_metrics = normalize_metrics(
actual_response["created_visualizations"]["objects"][0]["metrics"], exclude_keys=["title"]
)
expected_metrics = normalize_metrics(expected_response["metrics"], exclude_keys=["title"])
assert actual_metrics == expected_metrics, "Metrics do not match"
assert (
actual_response["created_visualizations"]["objects"][0]["visualization_type"]
== expected_response["visualizationType"]
), "Visualization type mismatch"
assert (
actual_response["created_visualizations"]["objects"][0]["dimensionality"] == expected_response["dimensionality"]
), "Dimensionality mismatch"
assert (
actual_response["created_visualizations"]["objects"][0]["filters"] == expected_response["filters"]
), "Filters mismatch"


@pytest.fixture(scope="module")
def app(set_authorization_header): # Using the global fixture for Authorization header
app_instance = gooddataAiChatApp(set_authorization_header, WORKSPACE_ID)
return app_instance


@pytest.mark.parametrize(
"question, expected_file",
[(item["question"], item["expected_objects_file"]) for item in questions_list],
ids=[item["question"] for item in questions_list],
)
@pytest.mark.asyncio
async def test_ai_chat(app, question, expected_file):
expected_objects = load_json(os.path.join(EXPECTED_OBJECTS_DIR, expected_file))
try:
api_response = await app.ask_question(question)
validate_response(api_response.to_dict(), expected_objects)

interaction_id = api_response.chat_history_interaction_id
user_feedback = await app.chat_history(interaction_id, "POSITIVE")
assert isinstance(user_feedback, ChatHistoryResult), "Invalid response from chat history"
except gooddata_api_client.ApiException as e:
pytest.fail(f"API exception: {e}")
except Exception as e:
pytest.fail(f"Unexpected error: {e}")


if __name__ == "__main__":
pytest.main()
36 changes: 36 additions & 0 deletions gooddata-sdk/integration_tests/scripts/ai_chat.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# (C) 2024 GoodData Corporation
import os
import sys

import pytest

# Add the root directory to sys.path
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

from env import HOST, TOKEN, WORKSPACE_ID
from gooddata_sdk import GoodDataSdk


@pytest.fixture
def test_config():
return {"host": HOST, "token": TOKEN, "workspace_id": WORKSPACE_ID}


questions = [
"What is the number of Accounts?",
"What is the total of Amount?",
]


@pytest.mark.parametrize("question", questions)
def test_ask_ai(test_config, question):
sdk = GoodDataSdk.create(host_=test_config["host"], token_=test_config["token"])
workspace_id = test_config["workspace_id"]
chat_ai_res = sdk.compute.ai_chat(workspace_id, question=question)

print(f"Chat AI response: {chat_ai_res}")
assert chat_ai_res is not None, "Response should not be None"


if __name__ == "__main__":
pytest.main()
26 changes: 26 additions & 0 deletions gooddata-sdk/integration_tests/scripts/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# (C) 2024 GoodData Corporation

import os
import sys

import pytest
from env import HOST, TOKEN
from gooddata_api_client import ApiClient, Configuration

# Add the root directory to the Python path
ROOT_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
sys.path.append(ROOT_DIR)


@pytest.fixture(scope="session", autouse=True)
def set_authorization_header():
"""
Fixture to set the Authorization header globally for all tests.
"""
configuration = Configuration(host=HOST)
configuration.access_token = TOKEN
api_client = ApiClient(configuration)
api_client.default_headers["Authorization"] = f"Bearer {TOKEN}"
yield api_client
# Cleanup after the tests, if necessary (e.g., closing client)
api_client.close()
16 changes: 16 additions & 0 deletions gooddata-sdk/integration_tests/scripts/create_ref_workspace.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# (C) 2024 GoodData Corporation
from env import DATASOURCE_ID, HOST, TOKEN, WORKSPACE_ID
from workspace_manager import createWorkspace, getDataSource, update_env_file

if __name__ == "__main__":
test_config = {"host": HOST, "token": TOKEN}

if WORKSPACE_ID:
print(f"Workspace ID '{WORKSPACE_ID}' already exists. Skipping workspace creation.")
else:
workspace_id = createWorkspace(test_config)
dataSource = getDataSource(DATASOURCE_ID, test_config)
if workspace_id:
update_env_file(workspace_id)
else:
print("Failed to create workspace.")
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# (C) 2024 GoodData Corporation
from env import HOST, TOKEN
from workspace_manager import deleteWorkspace

if __name__ == "__main__":
test_config = {"host": HOST, "token": TOKEN}

deleteWorkspace(test_config)
30 changes: 30 additions & 0 deletions gooddata-sdk/integration_tests/scripts/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# (C) 2024 GoodData Corporation
import json


def load_json(file_path):
"""Load a JSON file and return its contents."""
with open(file_path) as file: # Removed the "r" as it's the default mode
return json.load(file)


def normalize_metrics(metrics, exclude_keys=None):
"""
Normalize keys in the metrics list to camelCase, excluding specified keys.
:param metrics: List of dictionaries with metric data.
:param exclude_keys: List of keys to exclude from normalization.
:return: List of normalized metric dictionaries.
"""
if exclude_keys is None:
exclude_keys = []

def snake_to_camel(snake_str):
components = snake_str.split("_")
return components[0] + "".join(x.title() for x in components[1:])

return [
{snake_to_camel(key): value for key, value in metric.items() if key not in exclude_keys}
for metric in metrics
if isinstance(metric, dict)
]
Loading

0 comments on commit 6f4fa0f

Please sign in to comment.