orionis 0.280.0__py3-none-any.whl → 0.282.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- orionis/foundation/config/testing/entities/testing.py +9 -0
- orionis/metadata/framework.py +1 -1
- orionis/services/environment/dot_env.py +1 -1
- orionis/test/entities/test_result.py +7 -0
- orionis/test/logs/__init__.py +0 -0
- orionis/test/logs/log_test.py +211 -0
- orionis/test/suites/test_suite.py +2 -1
- orionis/test/suites/test_unit.py +85 -23
- orionis/test/view/__init__.py +0 -0
- orionis/test/view/index.html +127 -0
- {orionis-0.280.0.dist-info → orionis-0.282.0.dist-info}/METADATA +1 -1
- {orionis-0.280.0.dist-info → orionis-0.282.0.dist-info}/RECORD +16 -12
- {orionis-0.280.0.dist-info → orionis-0.282.0.dist-info}/WHEEL +0 -0
- {orionis-0.280.0.dist-info → orionis-0.282.0.dist-info}/licenses/LICENCE +0 -0
- {orionis-0.280.0.dist-info → orionis-0.282.0.dist-info}/top_level.txt +0 -0
- {orionis-0.280.0.dist-info → orionis-0.282.0.dist-info}/zip-safe +0 -0
@@ -127,6 +127,15 @@ class Testing:
|
|
127
127
|
}
|
128
128
|
)
|
129
129
|
|
130
|
+
persistent: bool = field(
|
131
|
+
default=False,
|
132
|
+
metadata={
|
133
|
+
"description": "Whether to keep the test results persistent. Default is False.",
|
134
|
+
"required": True,
|
135
|
+
"default": False
|
136
|
+
}
|
137
|
+
)
|
138
|
+
|
130
139
|
def __post_init__(self):
|
131
140
|
"""
|
132
141
|
Post-initialization validation for the testing configuration entity.
|
orionis/metadata/framework.py
CHANGED
@@ -79,7 +79,7 @@ class DotEnv(metaclass=Singleton):
|
|
79
79
|
"""
|
80
80
|
with self._lock:
|
81
81
|
serialized_value = self.__serializeValue(value)
|
82
|
-
set_key(str(self._resolved_path), key, serialized_value)
|
82
|
+
set_key(str(self._resolved_path), key, f'"{serialized_value}"', quote_mode="never")
|
83
83
|
os.environ[key] = str(value)
|
84
84
|
return True
|
85
85
|
|
File without changes
|
@@ -0,0 +1,211 @@
|
|
1
|
+
import json
|
2
|
+
import sqlite3
|
3
|
+
from pathlib import Path
|
4
|
+
from typing import Dict, List, Tuple, Optional
|
5
|
+
from contextlib import closing
|
6
|
+
from orionis.services.environment.env import Env
|
7
|
+
|
8
|
+
class LogTest:
|
9
|
+
"""
|
10
|
+
A utility class for managing test execution reports using a local SQLite database.
|
11
|
+
|
12
|
+
This class provides methods to create a report table, insert new test reports,
|
13
|
+
retrieve stored reports, reset the database, and close the connection.
|
14
|
+
|
15
|
+
Each test report is stored as a JSON string along with individual statistical fields
|
16
|
+
for easier filtering and analysis.
|
17
|
+
|
18
|
+
Attributes
|
19
|
+
----------
|
20
|
+
TABLE_NAME : str
|
21
|
+
The name of the database table where reports are stored.
|
22
|
+
DB_NAME : str
|
23
|
+
The filename of the SQLite database.
|
24
|
+
FIELDS : List[str]
|
25
|
+
List of expected keys in the report dictionary.
|
26
|
+
_conn : sqlite3.Connection or None
|
27
|
+
SQLite database connection instance.
|
28
|
+
"""
|
29
|
+
|
30
|
+
TABLE_NAME = "reportes"
|
31
|
+
DB_NAME = "tests.sqlite"
|
32
|
+
FIELDS = [
|
33
|
+
"json", "total_tests", "passed", "failed", "errors",
|
34
|
+
"skipped", "total_time", "success_rate", "timestamp"
|
35
|
+
]
|
36
|
+
|
37
|
+
def __init__(self, test_path_root: Optional[str] = None) -> None:
|
38
|
+
"""
|
39
|
+
Initialize the LogTest instance and configure the database path.
|
40
|
+
|
41
|
+
Parameters
|
42
|
+
----------
|
43
|
+
test_path_root : str, optional
|
44
|
+
Absolute or relative path to the directory where the SQLite file will be stored.
|
45
|
+
If None, the path is derived from the current file location.
|
46
|
+
|
47
|
+
Raises
|
48
|
+
------
|
49
|
+
ValueError
|
50
|
+
If the path cannot be resolved correctly.
|
51
|
+
"""
|
52
|
+
if test_path_root:
|
53
|
+
db_dir = Path(test_path_root).resolve()
|
54
|
+
else:
|
55
|
+
env_path = Env.get("TEST_PATH_ROOT")
|
56
|
+
if env_path:
|
57
|
+
db_dir = Path(env_path).resolve()
|
58
|
+
else:
|
59
|
+
db_dir = Path(__file__).resolve().parent
|
60
|
+
|
61
|
+
dbPath = db_dir.joinpath(self.DB_NAME)
|
62
|
+
Env.set("TEST_PATH_ROOT", str(dbPath).replace("\\", "\\\\"))
|
63
|
+
self._conn: Optional[sqlite3.Connection] = None
|
64
|
+
|
65
|
+
def __connect(self) -> None:
|
66
|
+
"""
|
67
|
+
Establish a connection to the SQLite database.
|
68
|
+
|
69
|
+
Raises
|
70
|
+
------
|
71
|
+
ConnectionError
|
72
|
+
If the connection to the database cannot be established.
|
73
|
+
"""
|
74
|
+
if self._conn is None:
|
75
|
+
try:
|
76
|
+
self._conn = sqlite3.connect(Env.get("TEST_PATH_ROOT"))
|
77
|
+
except sqlite3.Error as e:
|
78
|
+
raise ConnectionError(f"Database connection error: {e}")
|
79
|
+
|
80
|
+
def createTableIfNotExists(self) -> None:
|
81
|
+
"""
|
82
|
+
Create the 'reportes' table if it does not already exist.
|
83
|
+
|
84
|
+
The table includes fields for the full JSON report and individual
|
85
|
+
statistics for querying and filtering.
|
86
|
+
|
87
|
+
Raises
|
88
|
+
------
|
89
|
+
RuntimeError
|
90
|
+
If table creation fails due to a database error.
|
91
|
+
"""
|
92
|
+
self.__connect()
|
93
|
+
try:
|
94
|
+
with closing(self._conn.cursor()) as cursor:
|
95
|
+
cursor.execute(f'''
|
96
|
+
CREATE TABLE IF NOT EXISTS {self.TABLE_NAME} (
|
97
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
98
|
+
json TEXT NOT NULL,
|
99
|
+
total_tests INTEGER,
|
100
|
+
passed INTEGER,
|
101
|
+
failed INTEGER,
|
102
|
+
errors INTEGER,
|
103
|
+
skipped INTEGER,
|
104
|
+
total_time REAL,
|
105
|
+
success_rate REAL,
|
106
|
+
timestamp TEXT
|
107
|
+
)
|
108
|
+
''')
|
109
|
+
self._conn.commit()
|
110
|
+
except sqlite3.Error as e:
|
111
|
+
raise RuntimeError(f"Failed to create table: {e}")
|
112
|
+
|
113
|
+
def insertReport(self, report: Dict) -> None:
|
114
|
+
"""
|
115
|
+
Insert a test report into the database.
|
116
|
+
|
117
|
+
Parameters
|
118
|
+
----------
|
119
|
+
report : dict
|
120
|
+
Dictionary containing test statistics and metadata.
|
121
|
+
|
122
|
+
Required keys:
|
123
|
+
- total_tests : int
|
124
|
+
- passed : int
|
125
|
+
- failed : int
|
126
|
+
- errors : int
|
127
|
+
- skipped : int
|
128
|
+
- total_time : float
|
129
|
+
- success_rate : float
|
130
|
+
- timestamp : str (ISO format)
|
131
|
+
|
132
|
+
Raises
|
133
|
+
------
|
134
|
+
ValueError
|
135
|
+
If required keys are missing from the report.
|
136
|
+
RuntimeError
|
137
|
+
If insertion into the database fails.
|
138
|
+
"""
|
139
|
+
self.__connect()
|
140
|
+
missing = [key for key in self.FIELDS if key != "json" and key not in report]
|
141
|
+
if missing:
|
142
|
+
raise ValueError(f"Missing report fields: {missing}")
|
143
|
+
|
144
|
+
try:
|
145
|
+
with closing(self._conn.cursor()) as cursor:
|
146
|
+
cursor.execute(f'''
|
147
|
+
INSERT INTO {self.TABLE_NAME} (
|
148
|
+
json, total_tests, passed, failed, errors,
|
149
|
+
skipped, total_time, success_rate, timestamp
|
150
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
151
|
+
''', (
|
152
|
+
json.dumps(report),
|
153
|
+
report["total_tests"],
|
154
|
+
report["passed"],
|
155
|
+
report["failed"],
|
156
|
+
report["errors"],
|
157
|
+
report["skipped"],
|
158
|
+
report["total_time"],
|
159
|
+
report["success_rate"],
|
160
|
+
report["timestamp"]
|
161
|
+
))
|
162
|
+
self._conn.commit()
|
163
|
+
except sqlite3.Error as e:
|
164
|
+
raise RuntimeError(f"Failed to insert report: {e}")
|
165
|
+
|
166
|
+
def getReports(self) -> List[Tuple]:
|
167
|
+
"""
|
168
|
+
Retrieve all test reports from the database.
|
169
|
+
|
170
|
+
Returns
|
171
|
+
-------
|
172
|
+
List[Tuple]
|
173
|
+
A list of tuples representing each row in the `reportes` table.
|
174
|
+
|
175
|
+
Raises
|
176
|
+
------
|
177
|
+
RuntimeError
|
178
|
+
If retrieval fails due to a database error.
|
179
|
+
"""
|
180
|
+
self.__connect()
|
181
|
+
try:
|
182
|
+
with closing(self._conn.cursor()) as cursor:
|
183
|
+
cursor.execute(f'SELECT * FROM {self.TABLE_NAME}')
|
184
|
+
return cursor.fetchall()
|
185
|
+
except sqlite3.Error as e:
|
186
|
+
raise RuntimeError(f"Failed to retrieve reports: {e}")
|
187
|
+
|
188
|
+
def resetDatabase(self) -> None:
|
189
|
+
"""
|
190
|
+
Drop the `reportes` table, effectively clearing the report history.
|
191
|
+
|
192
|
+
Raises
|
193
|
+
------
|
194
|
+
RuntimeError
|
195
|
+
If table deletion fails.
|
196
|
+
"""
|
197
|
+
self.__connect()
|
198
|
+
try:
|
199
|
+
with closing(self._conn.cursor()) as cursor:
|
200
|
+
cursor.execute(f'DROP TABLE IF EXISTS {self.TABLE_NAME}')
|
201
|
+
self._conn.commit()
|
202
|
+
except sqlite3.Error as e:
|
203
|
+
raise RuntimeError(f"Failed to reset database: {e}")
|
204
|
+
|
205
|
+
def close(self) -> None:
|
206
|
+
"""
|
207
|
+
Close the SQLite database connection gracefully.
|
208
|
+
"""
|
209
|
+
if self._conn:
|
210
|
+
self._conn.close()
|
211
|
+
self._conn = None
|
@@ -58,7 +58,8 @@ class TestSuite(ITestSuite):
|
|
58
58
|
max_workers=config.max_workers,
|
59
59
|
fail_fast=config.fail_fast,
|
60
60
|
print_result=config.print_result,
|
61
|
-
throw_exception=config.throw_exception
|
61
|
+
throw_exception=config.throw_exception,
|
62
|
+
persistent=config.persistent,
|
62
63
|
)
|
63
64
|
|
64
65
|
# Extract configuration values
|
orionis/test/suites/test_unit.py
CHANGED
@@ -14,11 +14,14 @@ from rich.panel import Panel
|
|
14
14
|
from rich.syntax import Syntax
|
15
15
|
from rich.table import Table
|
16
16
|
from orionis.console.output.console import Console
|
17
|
+
from orionis.test.logs.log_test import LogTest
|
17
18
|
from orionis.test.suites.contracts.test_unit import IUnitTest
|
18
19
|
from orionis.test.entities.test_result import TestResult
|
19
20
|
from orionis.test.enums.test_mode import ExecutionMode
|
20
21
|
from orionis.test.enums.test_status import TestStatus
|
21
22
|
from orionis.test.exceptions.test_failure_exception import OrionisTestFailureException
|
23
|
+
from rich.live import Live
|
24
|
+
import os
|
22
25
|
|
23
26
|
class UnitTest(IUnitTest):
|
24
27
|
"""
|
@@ -63,6 +66,8 @@ class UnitTest(IUnitTest):
|
|
63
66
|
self.discovered_tests: List = []
|
64
67
|
self.width_output_component: int = int(self.rich_console.width * 0.75)
|
65
68
|
self.throw_exception: bool = False
|
69
|
+
self.persistent: bool = False
|
70
|
+
self.base_path: str = "tests"
|
66
71
|
|
67
72
|
def configure(
|
68
73
|
self,
|
@@ -71,7 +76,8 @@ class UnitTest(IUnitTest):
|
|
71
76
|
max_workers: int = None,
|
72
77
|
fail_fast: bool = None,
|
73
78
|
print_result: bool = None,
|
74
|
-
throw_exception: bool = False
|
79
|
+
throw_exception: bool = False,
|
80
|
+
persistent: bool = False
|
75
81
|
) -> 'UnitTest':
|
76
82
|
"""
|
77
83
|
Configures the UnitTest instance with the specified parameters.
|
@@ -82,6 +88,8 @@ class UnitTest(IUnitTest):
|
|
82
88
|
max_workers (int, optional): The maximum number of workers to use for parallel execution. Defaults to None.
|
83
89
|
fail_fast (bool, optional): Whether to stop execution upon the first failure. Defaults to None.
|
84
90
|
print_result (bool, optional): Whether to print the test results after execution. Defaults to None.
|
91
|
+
throw_exception (bool, optional): Whether to throw an exception if any test fails. Defaults to False.
|
92
|
+
persistent (bool, optional): Whether to persist the test results in a database. Defaults to False.
|
85
93
|
|
86
94
|
Returns:
|
87
95
|
UnitTest: The configured UnitTest instance.
|
@@ -106,6 +114,9 @@ class UnitTest(IUnitTest):
|
|
106
114
|
if throw_exception is not None:
|
107
115
|
self.throw_exception = throw_exception
|
108
116
|
|
117
|
+
if persistent is not None:
|
118
|
+
self.persistent = persistent
|
119
|
+
|
109
120
|
return self
|
110
121
|
|
111
122
|
def discoverTestsInFolder(
|
@@ -130,6 +141,8 @@ class UnitTest(IUnitTest):
|
|
130
141
|
ValueError: If the test folder does not exist, no tests are found, or an error occurs during test discovery.
|
131
142
|
"""
|
132
143
|
try:
|
144
|
+
self.base_path = base_path
|
145
|
+
|
133
146
|
full_path = Path(base_path) / folder_path
|
134
147
|
if not full_path.exists():
|
135
148
|
raise ValueError(f"Test folder not found: {full_path}")
|
@@ -258,15 +271,28 @@ class UnitTest(IUnitTest):
|
|
258
271
|
self.start_time = time.time()
|
259
272
|
self._startMessage()
|
260
273
|
|
261
|
-
#
|
262
|
-
|
263
|
-
|
274
|
+
# Elegant "running" message using Rich Panel
|
275
|
+
running_panel = Panel(
|
276
|
+
"[bold yellow]⏳ Running tests...[/bold yellow]\n[dim]This may take a few seconds. Please wait...[/dim]",
|
277
|
+
border_style="yellow",
|
278
|
+
title="In Progress",
|
279
|
+
title_align="left",
|
280
|
+
width=self.width_output_component,
|
281
|
+
padding=(1, 2)
|
282
|
+
)
|
264
283
|
|
265
|
-
#
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
284
|
+
# Print the panel and keep a reference to the live display
|
285
|
+
with Live(running_panel, console=self.rich_console, refresh_per_second=4, transient=True):
|
286
|
+
|
287
|
+
# Setup output capture
|
288
|
+
output_buffer = io.StringIO()
|
289
|
+
error_buffer = io.StringIO()
|
290
|
+
|
291
|
+
# Execute tests based on selected mode
|
292
|
+
if self.execution_mode == ExecutionMode.PARALLEL.value:
|
293
|
+
result = self._runTestsInParallel(output_buffer, error_buffer)
|
294
|
+
else:
|
295
|
+
result = self._runTestsSequentially(output_buffer, error_buffer)
|
270
296
|
|
271
297
|
# Process results
|
272
298
|
execution_time = time.time() - self.start_time
|
@@ -276,9 +302,6 @@ class UnitTest(IUnitTest):
|
|
276
302
|
if self.print_result:
|
277
303
|
self._displayResults(summary, result)
|
278
304
|
|
279
|
-
# Generate performance report
|
280
|
-
summary["timestamp"] = datetime.now().isoformat()
|
281
|
-
|
282
305
|
# Print Execution Time
|
283
306
|
if not result.wasSuccessful() and self.throw_exception:
|
284
307
|
raise OrionisTestFailureException(result)
|
@@ -432,6 +455,7 @@ class UnitTest(IUnitTest):
|
|
432
455
|
method=getattr(test, "_testMethodName", None),
|
433
456
|
module=getattr(test, "__module__", None),
|
434
457
|
file_path=inspect.getfile(test.__class__),
|
458
|
+
doc_string=getattr(getattr(test, test._testMethodName, None), "__doc__", None),
|
435
459
|
)
|
436
460
|
)
|
437
461
|
|
@@ -452,6 +476,7 @@ class UnitTest(IUnitTest):
|
|
452
476
|
method=getattr(test, "_testMethodName", None),
|
453
477
|
module=getattr(test, "__module__", None),
|
454
478
|
file_path=inspect.getfile(test.__class__),
|
479
|
+
doc_string=getattr(getattr(test, test._testMethodName, None), "__doc__", None),
|
455
480
|
)
|
456
481
|
)
|
457
482
|
|
@@ -472,6 +497,7 @@ class UnitTest(IUnitTest):
|
|
472
497
|
method=getattr(test, "_testMethodName", None),
|
473
498
|
module=getattr(test, "__module__", None),
|
474
499
|
file_path=inspect.getfile(test.__class__),
|
500
|
+
doc_string=getattr(getattr(test, test._testMethodName, None), "__doc__", None),
|
475
501
|
)
|
476
502
|
)
|
477
503
|
|
@@ -489,6 +515,7 @@ class UnitTest(IUnitTest):
|
|
489
515
|
method=getattr(test, "_testMethodName", None),
|
490
516
|
module=getattr(test, "__module__", None),
|
491
517
|
file_path=inspect.getfile(test.__class__),
|
518
|
+
doc_string=getattr(getattr(test, test._testMethodName, None), "__doc__", None),
|
492
519
|
)
|
493
520
|
)
|
494
521
|
|
@@ -519,11 +546,8 @@ class UnitTest(IUnitTest):
|
|
519
546
|
- "error_message" (str): The error message if the test failed or errored.
|
520
547
|
- "traceback" (str): The traceback information if the test failed or errored.
|
521
548
|
- "file_path" (str): The file path of the test.
|
522
|
-
- "performance_data" (List[Dict[str, float]]): A list containing performance data:
|
523
|
-
- "duration" (float): The total execution time of the test suite.
|
524
549
|
"""
|
525
550
|
test_details = []
|
526
|
-
performance_data = []
|
527
551
|
|
528
552
|
for test_result in result.test_results:
|
529
553
|
rst: TestResult = test_result
|
@@ -535,28 +559,66 @@ class UnitTest(IUnitTest):
|
|
535
559
|
'execution_time': float(rst.execution_time),
|
536
560
|
'error_message': rst.error_message,
|
537
561
|
'traceback': rst.traceback,
|
538
|
-
'file_path': rst.file_path
|
562
|
+
'file_path': rst.file_path,
|
563
|
+
'doc_string': rst.doc_string
|
539
564
|
})
|
540
565
|
|
541
|
-
performance_data.append({
|
542
|
-
'duration': float(execution_time)
|
543
|
-
})
|
544
|
-
|
545
566
|
passed = result.testsRun - len(result.failures) - len(result.errors) - len(result.skipped)
|
546
567
|
success_rate = (passed / result.testsRun * 100) if result.testsRun > 0 else 100.0
|
547
568
|
|
548
|
-
|
569
|
+
# Create a summary report
|
570
|
+
report = {
|
549
571
|
"total_tests": result.testsRun,
|
550
572
|
"passed": passed,
|
551
573
|
"failed": len(result.failures),
|
552
574
|
"errors": len(result.errors),
|
553
575
|
"skipped": len(result.skipped),
|
554
|
-
"total_time": execution_time,
|
576
|
+
"total_time": float(execution_time),
|
555
577
|
"success_rate": success_rate,
|
556
578
|
"test_details": test_details,
|
557
|
-
"
|
579
|
+
"timestamp": datetime.now().isoformat()
|
580
|
+
}
|
581
|
+
|
582
|
+
# Handle persistence of the report
|
583
|
+
if self.persistent:
|
584
|
+
self._persistTestResults(report)
|
585
|
+
|
586
|
+
# Return the summary
|
587
|
+
return {
|
588
|
+
"total_tests": result.testsRun,
|
589
|
+
"passed": passed,
|
590
|
+
"failed": len(result.failures),
|
591
|
+
"errors": len(result.errors),
|
592
|
+
"skipped": len(result.skipped),
|
593
|
+
"total_time": float(execution_time),
|
594
|
+
"success_rate": success_rate,
|
595
|
+
"test_details": test_details
|
558
596
|
}
|
559
597
|
|
598
|
+
def _persistTestResults(self, summary: Dict[str, Any]) -> None:
|
599
|
+
"""
|
600
|
+
Persists the test results in a SQLite database.
|
601
|
+
Args:
|
602
|
+
summary (Dict[str, Any]): A dictionary containing the test summary data.
|
603
|
+
Expected keys in the dictionary:
|
604
|
+
- "total_tests" (int): Total number of tests executed.
|
605
|
+
- "passed" (int): Number of tests that passed.
|
606
|
+
- "failed" (int): Number of tests that failed.
|
607
|
+
- "errors" (int): Number of tests that encountered errors.
|
608
|
+
- "skipped" (int): Number of tests that were skipped.
|
609
|
+
- "total_time" (float): Total duration of the test run in seconds.
|
610
|
+
- "success_rate" (float): Percentage of tests that passed.
|
611
|
+
Returns:
|
612
|
+
None
|
613
|
+
"""
|
614
|
+
full_path = os.path.abspath(os.path.join(os.getcwd(), self.base_path))
|
615
|
+
log = LogTest(test_path_root=full_path)
|
616
|
+
try:
|
617
|
+
log.createTableIfNotExists()
|
618
|
+
log.insertReport(summary)
|
619
|
+
finally:
|
620
|
+
log.close()
|
621
|
+
|
560
622
|
def _printSummaryTable(self, summary: Dict[str, Any]) -> None:
|
561
623
|
"""
|
562
624
|
Prints a summary table of test results using the Rich library.
|
File without changes
|
@@ -0,0 +1,127 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html lang="en">
|
3
|
+
|
4
|
+
<head>
|
5
|
+
<meta charset="UTF-8" />
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
7
|
+
<title>Orionis Test Dashboard</title>
|
8
|
+
<script src="https://cdn.tailwindcss.com"></script>
|
9
|
+
<link rel="icon" href="https://orionis-framework.com/svg/logo.svg" />
|
10
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
|
11
|
+
</head>
|
12
|
+
|
13
|
+
<body class="bg-gray-100 text-gray-800 font-sans">
|
14
|
+
<div class="max-w-7xl mx-auto px-4 sm:px-6 py-8">
|
15
|
+
|
16
|
+
<!-- Header -->
|
17
|
+
<header class="bg-gradient-to-r from-blue-900 to-cyan-400 text-white rounded-2xl shadow-xl p-6 mb-10">
|
18
|
+
<div class="flex flex-col md:flex-row justify-between items-center gap-4">
|
19
|
+
<div class="flex items-center gap-4">
|
20
|
+
<img src="https://orionis-framework.com/svg/logo.svg" alt="Orionis Logo"
|
21
|
+
class="h-10 brightness-0 invert" />
|
22
|
+
<h1 class="text-2xl font-light tracking-wider">Orionis Testing Results Dashboard</h1>
|
23
|
+
</div>
|
24
|
+
<div id="timestamp" class="text-sm text-white/90"></div>
|
25
|
+
</header>
|
26
|
+
|
27
|
+
<!-- Execution Summary Card -->
|
28
|
+
<div class="w-full mb-10">
|
29
|
+
<div class="bg-white rounded-2xl shadow-lg p-6 border-t-4 border-indigo-500 flex flex-col sm:flex-row items-center justify-between gap-4">
|
30
|
+
<div>
|
31
|
+
<div class="text-xs font-semibold text-gray-500 uppercase">Resumen de Ejecución</div>
|
32
|
+
<div class="text-lg font-bold text-gray-800 mt-2" id="execution-summary-title">Ejecución Completa</div>
|
33
|
+
<div class="text-sm text-gray-600 mt-1" id="execution-summary-desc">Todos los tests han sido ejecutados correctamente.</div>
|
34
|
+
</div>
|
35
|
+
<div class="flex items-center gap-4">
|
36
|
+
<div class="flex items-center gap-1 text-gray-700">
|
37
|
+
<i class="bi bi-clock-history text-xl text-indigo-500"></i>
|
38
|
+
<span id="execution-time">Duración: 00:00:00</span>
|
39
|
+
</div>
|
40
|
+
</div>
|
41
|
+
</div>
|
42
|
+
</div>
|
43
|
+
|
44
|
+
<!-- Summary Cards -->
|
45
|
+
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6 mb-10">
|
46
|
+
|
47
|
+
<div class="bg-white rounded-2xl shadow-lg p-6 border-t-4 border-green-500">
|
48
|
+
<div class="text-xs font-semibold text-gray-500 uppercase">Passed</div>
|
49
|
+
<div class="text-4xl font-bold text-gray-800 mt-2" id="passed">0</div>
|
50
|
+
<div class="mt-4 bg-gray-200 rounded-full h-2">
|
51
|
+
<div class="bg-green-500 h-2 rounded-full" id="passed-progress" style="width: 0%"></div>
|
52
|
+
</div>
|
53
|
+
</div>
|
54
|
+
|
55
|
+
<div class="bg-white rounded-2xl shadow-lg p-6 border-t-4 border-red-500">
|
56
|
+
<div class="text-xs font-semibold text-gray-500 uppercase">Failed</div>
|
57
|
+
<div class="text-4xl font-bold text-gray-800 mt-2" id="failed">0</div>
|
58
|
+
</div>
|
59
|
+
|
60
|
+
<div class="bg-white rounded-2xl shadow-lg p-6 border-t-4 border-yellow-500">
|
61
|
+
<div class="text-xs font-semibold text-gray-500 uppercase">Errors</div>
|
62
|
+
<div class="text-4xl font-bold text-gray-800 mt-2" id="errors">0</div>
|
63
|
+
</div>
|
64
|
+
|
65
|
+
<div class="bg-white rounded-2xl shadow-lg p-6 border-t-4 border-blue-500">
|
66
|
+
<div class="text-xs font-semibold text-gray-500 uppercase">Skipped</div>
|
67
|
+
<div class="text-4xl font-bold text-gray-800 mt-2" id="skipped">0</div>
|
68
|
+
</div>
|
69
|
+
|
70
|
+
</div>
|
71
|
+
|
72
|
+
<!-- Download Buttons & Select -->
|
73
|
+
<div class="flex flex-wrap justify-between items-center mb-10 gap-4">
|
74
|
+
<!-- Buttons to the left -->
|
75
|
+
<div class="flex flex-wrap gap-4">
|
76
|
+
<button id="download-json"
|
77
|
+
class="flex items-center gap-2 bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded shadow">
|
78
|
+
<i class="bi bi-file-earmark-code-fill text-lg"></i>
|
79
|
+
<span>Download JSON</span>
|
80
|
+
</button>
|
81
|
+
<button id="download-excel"
|
82
|
+
class="flex items-center gap-2 bg-green-600 hover:bg-green-700 text-white px-4 py-2 rounded shadow">
|
83
|
+
<i class="bi bi-file-earmark-excel-fill text-lg"></i>
|
84
|
+
<span>Download Excel</span>
|
85
|
+
</button>
|
86
|
+
<button id="download-pdf"
|
87
|
+
class="flex items-center gap-2 bg-red-600 hover:bg-red-700 text-white px-4 py-2 rounded shadow">
|
88
|
+
<i class="bi bi-file-earmark-pdf-fill text-lg"></i>
|
89
|
+
<span>Download PDF</span>
|
90
|
+
</button>
|
91
|
+
</div>
|
92
|
+
<!-- Elegant Select to the right -->
|
93
|
+
<div>
|
94
|
+
<select
|
95
|
+
class="appearance-none bg-white border border-gray-300 text-gray-700 py-2 px-4 pr-10 rounded-lg shadow focus:outline-none focus:ring-2 focus:ring-blue-400 focus:border-blue-400 transition text-base font-medium">
|
96
|
+
<option selected disabled>Selecciona una opción</option>
|
97
|
+
<option>Opción 1</option>
|
98
|
+
<option>Opción 2</option>
|
99
|
+
<option>Opción 3</option>
|
100
|
+
</select>
|
101
|
+
</div>
|
102
|
+
</div>
|
103
|
+
|
104
|
+
<!-- JSON Viewer Panel -->
|
105
|
+
<div class="bg-white rounded-2xl shadow-xl p-6">
|
106
|
+
<h2 class="text-xl font-semibold mb-4">Test Report JSON</h2>
|
107
|
+
<pre id="json-data"
|
108
|
+
class="whitespace-pre-wrap text-sm text-gray-800 bg-gray-100 p-4 rounded-xl overflow-x-auto"></pre>
|
109
|
+
</div>
|
110
|
+
|
111
|
+
<!-- Footer -->
|
112
|
+
<footer class="mt-12 text-center text-gray-500 text-sm py-6">
|
113
|
+
Developed with the power of
|
114
|
+
<a href="https://orionis-framework.com/" target="_blank" rel="noopener"
|
115
|
+
class="font-semibold text-blue-700 hover:underline">
|
116
|
+
Orionis Framework
|
117
|
+
</a>
|
118
|
+
<i class="bi bi-stars text-yellow-400 align-middle ml-1"></i>
|
119
|
+
</footer>
|
120
|
+
|
121
|
+
<script>
|
122
|
+
// Placeholder JS logic
|
123
|
+
document.getElementById("timestamp").textContent = new Date().toLocaleString();
|
124
|
+
</script>
|
125
|
+
</body>
|
126
|
+
|
127
|
+
</html>
|
@@ -222,11 +222,11 @@ orionis/foundation/config/session/enums/same_site_policy.py,sha256=Oo05CJ-5keJWz
|
|
222
222
|
orionis/foundation/config/session/helpers/secret_key.py,sha256=yafjzQ9KVQdXzCQCMthpgizlNCo5F5UTLtAnInipUMk,447
|
223
223
|
orionis/foundation/config/testing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
224
224
|
orionis/foundation/config/testing/entities/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
225
|
-
orionis/foundation/config/testing/entities/testing.py,sha256=
|
225
|
+
orionis/foundation/config/testing/entities/testing.py,sha256=pIMb2DCwvu8K37ryiMXqOcS4VGDw0W8dJD-N37na8LM,10251
|
226
226
|
orionis/foundation/config/testing/enums/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
227
227
|
orionis/foundation/config/testing/enums/test_mode.py,sha256=IbFpauu7J-iSAfmC8jDbmTEYl8eZr-AexL-lyOh8_74,337
|
228
228
|
orionis/metadata/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
229
|
-
orionis/metadata/framework.py,sha256=
|
229
|
+
orionis/metadata/framework.py,sha256=iiVK0TMbgywwCmj-Hc3MX3kz129Ks9DKvrUs-eIvsRs,4960
|
230
230
|
orionis/metadata/package.py,sha256=5p4fxjPpaktsreJ458pAl-Oi1363MWACPQvqXi_N6oA,6704
|
231
231
|
orionis/patterns/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
232
232
|
orionis/patterns/singleton/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -235,7 +235,7 @@ orionis/services/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,
|
|
235
235
|
orionis/services/asynchrony/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
236
236
|
orionis/services/asynchrony/async_io.py,sha256=22rHi-sIHL3ESHpxKFps8O-9O_5Uoq-BbtZpMmgFTrA,1023
|
237
237
|
orionis/services/environment/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
238
|
-
orionis/services/environment/dot_env.py,sha256=
|
238
|
+
orionis/services/environment/dot_env.py,sha256=Ok3M-yKzsVcVFe6KTjqkuVe-3UTAuS-vnFSbKstetxY,9068
|
239
239
|
orionis/services/environment/env.py,sha256=IV5iUQRwGOlL8P0xi4RL-ybc2qGRCXBMokRm2AuXhyc,2035
|
240
240
|
orionis/services/environment/contracts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
241
241
|
orionis/services/environment/contracts/env.py,sha256=5g7jppzR5_ln8HlxJu2r0sSxLOkvBfSk3t4x_BKkYYk,1811
|
@@ -305,24 +305,28 @@ orionis/test/cases/test_async.py,sha256=YwycimGfUx9-kd_FFO8BZXyVayYwOBOzxbu3WZU_
|
|
305
305
|
orionis/test/cases/test_case.py,sha256=GVN3cxYuE47uPOEqFUiMreLdXjTyqzHjjxfyEM5_D4w,1446
|
306
306
|
orionis/test/cases/test_sync.py,sha256=FekXNQDq5pOQB2YpvP7E9jAqIJH9uZhTMoPz-qx8FO0,742
|
307
307
|
orionis/test/entities/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
308
|
-
orionis/test/entities/test_result.py,sha256=
|
308
|
+
orionis/test/entities/test_result.py,sha256=4ZThMeYRus6Vabnn2MW4hFztIRJA0_W5R2rSJIw90Ho,2095
|
309
309
|
orionis/test/enums/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
310
310
|
orionis/test/enums/test_mode.py,sha256=CHstYZ180MEX84AjZIoyA1l8gKxFLp_eciLOj2in57E,481
|
311
311
|
orionis/test/enums/test_status.py,sha256=vNKRmp1lud_ZGTayf3A8wO_0vEYdFABy_oMw-RcEc1c,673
|
312
312
|
orionis/test/exceptions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
313
313
|
orionis/test/exceptions/test_config_exception.py,sha256=yJv0JUTkZcF0Z4J8UHtffUipNgwNgmLhaqlOnnXFLyQ,995
|
314
314
|
orionis/test/exceptions/test_failure_exception.py,sha256=pcMhzF1Z5kkJm4yM7gZiQI0SvGjKFixJkRd6VBE03AY,2199
|
315
|
+
orionis/test/logs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
316
|
+
orionis/test/logs/log_test.py,sha256=dUFjtryTsptxipM7RY2fksv9CPtuvPq045Fx7kCaN0o,7176
|
315
317
|
orionis/test/output/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
316
318
|
orionis/test/output/dumper.py,sha256=pHD_HeokfWUydvaxxSvdJHdm2-VsrgL0PBr9ZSaavd8,3749
|
317
319
|
orionis/test/output/contracts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
318
320
|
orionis/test/output/contracts/dumper.py,sha256=ZpzlSJixGNbjFUVl53mCg_5djC-xwiit4ozQlzUps4g,1161
|
319
321
|
orionis/test/suites/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
320
|
-
orionis/test/suites/test_suite.py,sha256=
|
321
|
-
orionis/test/suites/test_unit.py,sha256=
|
322
|
+
orionis/test/suites/test_suite.py,sha256=q3k-R40Vigh4xyXYHQdTAyShcqQeitH4oMKsQvUG8Kc,4871
|
323
|
+
orionis/test/suites/test_unit.py,sha256=CUwjt-FeWLi7ME9xpwpGucEtC4TM6vPXEicaPCJBwyo,44593
|
322
324
|
orionis/test/suites/contracts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
323
325
|
orionis/test/suites/contracts/test_suite.py,sha256=arJSxWGjOHTVGiJrmqdqDrb9Ymt3SitDPZKVMBsWCf8,888
|
324
326
|
orionis/test/suites/contracts/test_unit.py,sha256=5gaGXqCx07aDlAQJi8J-GF83kVj3uIx_fdPF1HYMKcg,5596
|
325
|
-
orionis
|
327
|
+
orionis/test/view/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
328
|
+
orionis/test/view/index.html,sha256=U4XYO4hA-mAJCK1gcVRgIysmISK3g3Vgi2ntLofFAhE,6592
|
329
|
+
orionis-0.282.0.dist-info/licenses/LICENCE,sha256=-_4cF2EBKuYVS_SQpy1uapq0oJPUU1vl_RUWSy2jJTo,1111
|
326
330
|
tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
327
331
|
tests/example/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
328
332
|
tests/example/test_example.py,sha256=DUZU6lWVFsyHtBEXx0cBxMrSCpOtAOL3PUoi9-tXAas,583
|
@@ -420,8 +424,8 @@ tests/support/inspection/fakes/fake_reflection_instance_with_abstract.py,sha256=
|
|
420
424
|
tests/testing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
421
425
|
tests/testing/test_testing_result.py,sha256=54QDn6_v9feIcDcA6LPXcvYhlt_X8JqLGTYWS9XjKXM,3029
|
422
426
|
tests/testing/test_testing_unit.py,sha256=MeVL4gc4cGRPXdVOOKJx6JPKducrZ8cN7F52Iiciixg,5443
|
423
|
-
orionis-0.
|
424
|
-
orionis-0.
|
425
|
-
orionis-0.
|
426
|
-
orionis-0.
|
427
|
-
orionis-0.
|
427
|
+
orionis-0.282.0.dist-info/METADATA,sha256=frb3YgJcrRlKnVfgTVaDpAGsv_rSkMstd4j7IRiuOZI,4772
|
428
|
+
orionis-0.282.0.dist-info/WHEEL,sha256=Nw36Djuh_5VDukK0H78QzOX-_FQEo6V37m3nkm96gtU,91
|
429
|
+
orionis-0.282.0.dist-info/top_level.txt,sha256=2bdoHgyGZhOtLAXS6Om8OCTmL24dUMC_L1quMe_ETbk,14
|
430
|
+
orionis-0.282.0.dist-info/zip-safe,sha256=frcCV1k9oG9oKj3dpUqdJg1PxRT2RSN_XKdLCPjaYaY,2
|
431
|
+
orionis-0.282.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|