Skip to content

Commit 6f4fa0f

Browse files
committed
feat: POC automation test
JIRA: QA-23477 risk: nonprod
1 parent a576d4f commit 6f4fa0f

15 files changed

+456
-1
lines changed

.python-version

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
3.12.4
1+
3.11.4

gooddata-sdk/env.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# (C) 2024 GoodData Corporation
2+
# env.py
3+
import os
4+
5+
# Define environment variables
6+
HOST = os.getenv("GOODDATA_HOST", "https://checklist.staging.stg11.panther.intgdc.com")
7+
TOKEN = os.getenv("GOODDATA_TOKEN", "<your token>")
8+
DATASOURCE_ID = os.getenv("DATASOURCE_ID", "your datasource")
9+
WORKSPACE_ID = "your workspace"
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
{
2+
"id": "total_returns_per_month",
3+
"title": "Total Returns per Month",
4+
"visualizationType": "COLUMN",
5+
"metrics": [
6+
{
7+
"id": "total_returns",
8+
"type": "metric",
9+
"title": "Total Returns"
10+
}
11+
],
12+
"dimensionality": [
13+
{
14+
"id": "return_date.month",
15+
"type": "attribute",
16+
"title": "Return date - Month/Year"
17+
}
18+
],
19+
"filters": [],
20+
"suggestions": [
21+
{
22+
"query": "Switch to a line chart to better visualize the trend of total returns over the months.",
23+
"label": "Line Chart for Trends"
24+
},
25+
{
26+
"query": "Filter the data to show total returns for this year only.",
27+
"label": "This Year's Returns"
28+
}
29+
]
30+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
{
2+
"id": "total_returns_per_month_current_year",
3+
"title": "Total Returns Per Month - Current Year",
4+
"visualizationType": "COLUMN",
5+
"metrics": [
6+
{
7+
"id": "total_returns",
8+
"type": "metric",
9+
"title": "Total Returns"
10+
}
11+
],
12+
"dimensionality": [
13+
{
14+
"id": "return_date.month",
15+
"type": "attribute",
16+
"title": "Return date - Month/Year"
17+
}
18+
],
19+
"filters": [
20+
{
21+
"using": "return_date",
22+
"granularity": "YEAR",
23+
"_from": 0,
24+
"to": 0
25+
}
26+
],
27+
"suggestions": [
28+
{
29+
"query": "Switch to a line chart to better represent the trend of total returns over months.",
30+
"label": "Show Line Chart Trend"
31+
}
32+
]
33+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
{
2+
"id": "total_customers_per_month_current_year",
3+
"title": "Total Customers per Month - Current Year",
4+
"visualizationType": "LINE",
5+
"metrics": [
6+
{
7+
"id": "total_customers",
8+
"type": "metric",
9+
"title": "Total Customers"
10+
}
11+
],
12+
"dimensionality": [
13+
{
14+
"id": "date.month",
15+
"type": "attribute",
16+
"title": "Date - Month/Year"
17+
}
18+
],
19+
"filters": [
20+
{
21+
"using": "date",
22+
"granularity": "YEAR",
23+
"_from": 0,
24+
"to": 0
25+
}
26+
],
27+
"suggestions": [
28+
{
29+
"query": "Consider switching to a COLUMN chart for clearer comparisons of Total Customers per month.",
30+
"label": "Switch to COLUMN chart"
31+
}
32+
]
33+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
{
2+
"id": "total_returns_per_month_current_year",
3+
"title": "Total Returns Per Month - Current Year",
4+
"visualizationType": "LINE",
5+
"metrics": [
6+
{
7+
"id": "total_returns",
8+
"type": "metric",
9+
"title": "Total Returns"
10+
}
11+
],
12+
"dimensionality": [
13+
{
14+
"id": "return_date.month",
15+
"type": "attribute",
16+
"title": "Return date - Month/Year"
17+
}
18+
],
19+
"filters": [
20+
{
21+
"using": "return_date",
22+
"granularity": "YEAR",
23+
"_from": 0,
24+
"to": 0
25+
}
26+
],
27+
"suggestions": [
28+
{
29+
"query": "Switch to a line chart to better represent the trend of total returns over months.",
30+
"label": "Show Line Chart Trend"
31+
}
32+
]
33+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
[
2+
{
3+
"question": "What is total returns per month? show as COLUMN chart",
4+
"expected_objects_file": "column_total_returns_by_month.json"
5+
},
6+
{
7+
"question": "Add filter by current year, show as COLUMN chart",
8+
"expected_objects_file": "column_total_returns_by_month_filter_current_year.json"
9+
},
10+
{
11+
"question": "Switch to LINE chart",
12+
"expected_objects_file": "line_total_returns_by_month_filter_current_year.json"
13+
},
14+
{
15+
"question": "Replace metric Total Customers instead of total returns",
16+
"expected_objects_file": "line_total_customers_by_month_filter_current_year.json"
17+
}
18+
]
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
# (C) 2024 GoodData Corporation
2+
import os
3+
import sys
4+
5+
import gooddata_api_client
6+
import pytest
7+
from gooddata_api_client.api import smart_functions_api
8+
from gooddata_api_client.model.chat_history_request import ChatHistoryRequest
9+
from gooddata_api_client.model.chat_history_result import ChatHistoryResult
10+
from gooddata_api_client.model.chat_request import ChatRequest
11+
from utils import load_json, normalize_metrics
12+
13+
sys.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
14+
15+
from env import WORKSPACE_ID
16+
17+
EXPECTED_OBJECTS_DIR = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "data_response")
18+
QUESTIONS_DIR = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "fixtures")
19+
20+
questions_list = load_json(os.path.join(QUESTIONS_DIR, "ai_questions.json"))
21+
22+
23+
class gooddataAiChatApp:
24+
def __init__(self, api_client, workspace_id):
25+
self.api_instance = smart_functions_api.SmartFunctionsApi(api_client)
26+
self.workspace_id = workspace_id
27+
28+
async def ask_question(self, question: str):
29+
return self.api_instance.ai_chat(self.workspace_id, ChatRequest(question=question))
30+
31+
async def chat_history(self, interaction_id: int, feedback: str):
32+
return self.api_instance.ai_chat_history(
33+
self.workspace_id, ChatHistoryRequest(chat_history_interaction_id=interaction_id, user_feedback=feedback)
34+
)
35+
36+
37+
def validate_response(actual_response, expected_response):
38+
actual_metrics = normalize_metrics(
39+
actual_response["created_visualizations"]["objects"][0]["metrics"], exclude_keys=["title"]
40+
)
41+
expected_metrics = normalize_metrics(expected_response["metrics"], exclude_keys=["title"])
42+
assert actual_metrics == expected_metrics, "Metrics do not match"
43+
assert (
44+
actual_response["created_visualizations"]["objects"][0]["visualization_type"]
45+
== expected_response["visualizationType"]
46+
), "Visualization type mismatch"
47+
assert (
48+
actual_response["created_visualizations"]["objects"][0]["dimensionality"] == expected_response["dimensionality"]
49+
), "Dimensionality mismatch"
50+
assert (
51+
actual_response["created_visualizations"]["objects"][0]["filters"] == expected_response["filters"]
52+
), "Filters mismatch"
53+
54+
55+
@pytest.fixture(scope="module")
56+
def app(set_authorization_header): # Using the global fixture for Authorization header
57+
app_instance = gooddataAiChatApp(set_authorization_header, WORKSPACE_ID)
58+
return app_instance
59+
60+
61+
@pytest.mark.parametrize(
62+
"question, expected_file",
63+
[(item["question"], item["expected_objects_file"]) for item in questions_list],
64+
ids=[item["question"] for item in questions_list],
65+
)
66+
@pytest.mark.asyncio
67+
async def test_ai_chat(app, question, expected_file):
68+
expected_objects = load_json(os.path.join(EXPECTED_OBJECTS_DIR, expected_file))
69+
try:
70+
api_response = await app.ask_question(question)
71+
validate_response(api_response.to_dict(), expected_objects)
72+
73+
interaction_id = api_response.chat_history_interaction_id
74+
user_feedback = await app.chat_history(interaction_id, "POSITIVE")
75+
assert isinstance(user_feedback, ChatHistoryResult), "Invalid response from chat history"
76+
except gooddata_api_client.ApiException as e:
77+
pytest.fail(f"API exception: {e}")
78+
except Exception as e:
79+
pytest.fail(f"Unexpected error: {e}")
80+
81+
82+
if __name__ == "__main__":
83+
pytest.main()
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# (C) 2024 GoodData Corporation
2+
import os
3+
import sys
4+
5+
import pytest
6+
7+
# Add the root directory to sys.path
8+
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
9+
10+
from env import HOST, TOKEN, WORKSPACE_ID
11+
from gooddata_sdk import GoodDataSdk
12+
13+
14+
@pytest.fixture
15+
def test_config():
16+
return {"host": HOST, "token": TOKEN, "workspace_id": WORKSPACE_ID}
17+
18+
19+
questions = [
20+
"What is the number of Accounts?",
21+
"What is the total of Amount?",
22+
]
23+
24+
25+
@pytest.mark.parametrize("question", questions)
26+
def test_ask_ai(test_config, question):
27+
sdk = GoodDataSdk.create(host_=test_config["host"], token_=test_config["token"])
28+
workspace_id = test_config["workspace_id"]
29+
chat_ai_res = sdk.compute.ai_chat(workspace_id, question=question)
30+
31+
print(f"Chat AI response: {chat_ai_res}")
32+
assert chat_ai_res is not None, "Response should not be None"
33+
34+
35+
if __name__ == "__main__":
36+
pytest.main()
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# (C) 2024 GoodData Corporation
2+
3+
import os
4+
import sys
5+
6+
import pytest
7+
from env import HOST, TOKEN
8+
from gooddata_api_client import ApiClient, Configuration
9+
10+
# Add the root directory to the Python path
11+
ROOT_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
12+
sys.path.append(ROOT_DIR)
13+
14+
15+
@pytest.fixture(scope="session", autouse=True)
16+
def set_authorization_header():
17+
"""
18+
Fixture to set the Authorization header globally for all tests.
19+
"""
20+
configuration = Configuration(host=HOST)
21+
configuration.access_token = TOKEN
22+
api_client = ApiClient(configuration)
23+
api_client.default_headers["Authorization"] = f"Bearer {TOKEN}"
24+
yield api_client
25+
# Cleanup after the tests, if necessary (e.g., closing client)
26+
api_client.close()

0 commit comments

Comments
 (0)