From bcf933ecfdad632ed844ad283c978ab2680a6591 Mon Sep 17 00:00:00 2001
From: cedricdcc <30471340+cedricdcc@users.noreply.github.com>
Date: Wed, 9 Oct 2024 14:32:36 +0200
Subject: [PATCH] first push for sema feature tackles issue #111 , #112
---
results.csv | 3 +
results.html | 1 +
results.yml | 12 ++
sema/check/README.md | 70 ++++++++++
sema/check/__init__.py | 17 +++
sema/check/__main__.py | 72 ++++++++++
sema/check/check.py | 61 ++++++++
sema/check/service.py | 60 ++++++++
sema/check/sinks/__init__.py | 11 ++
sema/check/sinks/csv_sink.py | 23 ++++
sema/check/sinks/html_sink.py | 19 +++
sema/check/sinks/yml_sink.py | 10 ++
sema/check/testing/base.py | 31 +++++
sema/check/testing/test_example.py | 42 ++++++
sema/subyt/__main__.py | 6 +-
tests/check/out/output.csv | 3 +
tests/check/out/output.yml | 10 ++
tests/check/out/test_results.html | 1 +
tests/check/test_files/bad_formatted_yml.yml | 0
tests/check/test_files/good_yaml.yml | 9 ++
.../test_files/should not be processed.txt | 1 +
tests/check/test_sema_check_cli.py | 25 ++++
tests/check/test_service.py | 61 ++++++++
tests/check/test_sinks.py | 130 ++++++++++++++++++
tests/check/test_tesing_factory.py | 0
25 files changed, 675 insertions(+), 3 deletions(-)
create mode 100644 results.csv
create mode 100644 results.html
create mode 100644 results.yml
create mode 100644 sema/check/README.md
create mode 100644 sema/check/__init__.py
create mode 100644 sema/check/__main__.py
create mode 100644 sema/check/check.py
create mode 100644 sema/check/service.py
create mode 100644 sema/check/sinks/__init__.py
create mode 100644 sema/check/sinks/csv_sink.py
create mode 100644 sema/check/sinks/html_sink.py
create mode 100644 sema/check/sinks/yml_sink.py
create mode 100644 sema/check/testing/base.py
create mode 100644 sema/check/testing/test_example.py
create mode 100644 tests/check/out/output.csv
create mode 100644 tests/check/out/output.yml
create mode 100644 tests/check/out/test_results.html
create mode 100644 tests/check/test_files/bad_formatted_yml.yml
create mode 100644 tests/check/test_files/good_yaml.yml
create mode 100644 tests/check/test_files/should not be processed.txt
create mode 100644 tests/check/test_sema_check_cli.py
create mode 100644 tests/check/test_service.py
create mode 100644 tests/check/test_sinks.py
create mode 100644 tests/check/test_tesing_factory.py
diff --git a/results.csv b/results.csv
new file mode 100644
index 0000000..4ad9861
--- /dev/null
+++ b/results.csv
@@ -0,0 +1,3 @@
+success,error,message
+True,False,Example test passed.
+True,False,Example test passed.
diff --git a/results.html b/results.html
new file mode 100644
index 0000000..1b0c966
--- /dev/null
+++ b/results.html
@@ -0,0 +1 @@
+
Test ResultsSuccess | Error | Message |
---|
True | False | Example test passed. |
True | False | Example test passed. |
\ No newline at end of file
diff --git a/results.yml b/results.yml
new file mode 100644
index 0000000..082ad3b
--- /dev/null
+++ b/results.yml
@@ -0,0 +1,12 @@
+- error: false
+ message: Example test passed.
+ success: true
+ type: example
+ type_test: example
+ url: http://example.com
+- error: false
+ message: Example test passed.
+ success: true
+ type: example
+ type_test: example
+ url: http://another.com/another
diff --git a/sema/check/README.md b/sema/check/README.md
new file mode 100644
index 0000000..8a62c7f
--- /dev/null
+++ b/sema/check/README.md
@@ -0,0 +1,70 @@
+Yaml for tests will look like
+
+```yaml
+- url: "http://example.com"
+ type: "example"
+ options:
+ param1: "value1"
+ param2: "value2"
+- url: "http://another.com"
+ type: "another_test"
+ options:
+ paramA: "valueA"
+```
+
+where `url` is the URL to test, `type` is the test type, and `options` are the test parameters.
+
+## Testing base class
+
+The TestBase is a dataclass that holds the test data. It has the following fields:
+
+- `url` - the URL to test
+- `type` - the test type
+- `options` - the test parameters
+- `result` - the test result
+ - `success`: boolean, whether the test was successful
+ - `message`: string, a message describing the test result, this can also be the error message
+ - `error`: boolean, whether the test failed due to an error
+
+## flow of sema-check
+
+```mermaid
+graph TD
+ A[User] -->|Invokes CLI| B["CLI Module (__main__.py)"]
+ B --> C["Argument Parser"]
+ C --> D["Service Module (service.py)"]
+ D --> E["Load YAML Files"]
+ D --> F["Instantiate Test Classes"]
+ F --> G["Test Classes (tests/)"]
+ G --> H["Execute Tests"]
+ H --> I["Collect Test Results"]
+ I --> J["Sink Modules (sinks/)"]
+ J --> K["CSV Sink (csv_sink.py)"]
+ J --> L["HTML Sink (html_sink.py)"]
+ J --> M["YML Sink (yml_sink.py)"]
+ K --> N["Output: results.csv"]
+ L --> O["Output: results.html"]
+ M --> P["Output: results.yml"]
+ D --> Q["Utilities (utils.py)"]
+ Q --> R["Utility Functions"]
+
+ %% Styling Nodes
+ style A fill:#f9f,stroke:#333,stroke-width:2px
+ style B fill:#bbf,stroke:#333,stroke-width:2px
+ style C fill:#bbf,stroke:#333,stroke-width:2px
+ style D fill:#bbf,stroke:#333,stroke-width:2px
+ style E fill:#bfb,stroke:#333,stroke-width:2px
+ style F fill:#bfb,stroke:#333,stroke-width:2px
+ style G fill:#fbf,stroke:#333,stroke-width:2px
+ style H fill:#fbf,stroke:#333,stroke-width:2px
+ style I fill:#fbb,stroke:#333,stroke-width:2px
+ style J fill:#ffb,stroke:#333,stroke-width:2px
+ style K fill:#bff,stroke:#333,stroke-width:2px
+ style L fill:#bff,stroke:#333,stroke-width:2px
+ style M fill:#bff,stroke:#333,stroke-width:2px
+ style N fill:#cff,stroke:#333,stroke-width:2px
+ style O fill:#cff,stroke:#333,stroke-width:2px
+ style P fill:#cff,stroke:#333,stroke-width:2px
+ style Q fill:#fdf,stroke:#333,stroke-width:2px
+ style R fill:#dfd,stroke:#333,stroke-width:2px
+```
diff --git a/sema/check/__init__.py b/sema/check/__init__.py
new file mode 100644
index 0000000..4772a99
--- /dev/null
+++ b/sema/check/__init__.py
@@ -0,0 +1,17 @@
+# your_submodule/__init__.py
+
+from .service import run_tests
+from .testing.base import TestBase
+from .check import Check
+from .sinks.csv_sink import write_csv
+from .sinks.html_sink import write_html
+from .sinks.yml_sink import write_yml
+
+__all__ = [
+ "run_tests",
+ "Check",
+ "TestBase",
+ "write_csv",
+ "write_html",
+ "write_yml",
+]
diff --git a/sema/check/__main__.py b/sema/check/__main__.py
new file mode 100644
index 0000000..060b69f
--- /dev/null
+++ b/sema/check/__main__.py
@@ -0,0 +1,72 @@
+# your_submodule/__main__.py
+
+# your_submodule/cli.py
+import sys
+from logging import getLogger
+
+from sema.commons.cli import Namespace, SemaArgsParser
+from sema.check import Check
+
+log = getLogger(__name__)
+
+
+def get_arg_parser() -> SemaArgsParser:
+ """
+ Defines the arguments to this script by using Python's
+ [argparse](https://docs.python.org/3/library/argparse.html)
+ """
+ parser = SemaArgsParser(
+ "sema-check",
+ "Run tests based on YAML configurations.",
+ )
+
+ parser.add_argument(
+ "-i",
+ "--input_folder",
+ action="store",
+ required=True,
+ help="Path to the folder containing YAML files.",
+ )
+
+ parser.add_argument(
+ "-o",
+ "--output",
+ choices=["csv", "html", "yml"],
+ default="csv",
+ help="Output format for the test results.",
+ )
+
+ return parser
+
+
+def make_service(args: Namespace) -> Check:
+ return Check(
+ input_folder=args.input_folder,
+ output=args.output,
+ )
+
+
+def _main(*args_list) -> bool:
+ log.info(f"Running sema-check with args: {args_list}")
+ args = get_arg_parser().parse_args(args_list)
+
+ try:
+ check = make_service(args)
+ r = check.process()
+ print(f"Finished running tests: {r}")
+ return bool(r)
+ except Exception as e:
+ log.error(f"An error occurred: {e}")
+ return False
+
+
+def main(args=None):
+ log.debug(f"Running sema-check with args: {args[1:]}")
+ success: bool = _main(*args[1:])
+ log.debug(f"Finished running sema-check")
+ log.info(f"Success: {success}")
+ # sys.exit(0 if success else 1)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/sema/check/check.py b/sema/check/check.py
new file mode 100644
index 0000000..5709f80
--- /dev/null
+++ b/sema/check/check.py
@@ -0,0 +1,61 @@
+import logging
+from sema.commons.service import ServiceBase, ServiceResult, Trace
+from .service import run_tests
+from .sinks import write_csv, write_html, write_yml
+
+log = logging.getLogger(__name__)
+
+
+class CheckResult(ServiceResult):
+ """Result of the check service"""
+
+ def __init__(self):
+ self._success = False
+
+ @property
+ def success(self) -> bool:
+ return self._success
+
+
+class Check(ServiceBase):
+ """The main class for the check service."""
+
+ def __init__(self, *, input_folder: str, output: str) -> None:
+ """Initialize the Check Service object
+
+ :param input_folder: the folder where the files to be checked are located
+ :type input_folder: str
+ :param output: the output format to be used
+ :type output: str
+ """
+ self.input_folder = input_folder
+ self.output = output
+
+ log.debug(
+ f"Check service initialized with input_folder: {input_folder}, output: {output}"
+ )
+
+ assert self.input_folder, "input_folder not provided"
+ assert self.output, "output not provided"
+
+ self._result = CheckResult()
+
+ @Trace.init(Trace)
+ def process(self) -> None:
+ """Process the check service"""
+ try:
+ results = run_tests(self.input_folder)
+ log.debug(f"Test results: {results}")
+ if self.output == "csv":
+ write_csv(results, "results.csv")
+ elif self.output == "html":
+ write_html(results, "results.html")
+ elif self.output == "yml":
+ write_yml(results, "results.yml")
+ log.debug(f"Test results written to results.{self.output}")
+ self._result._success = True
+ except Exception as e:
+ log.error(f"An error occurred: {e}")
+ return self._result._success
+ finally:
+ return self._result._success
diff --git a/sema/check/service.py b/sema/check/service.py
new file mode 100644
index 0000000..e626006
--- /dev/null
+++ b/sema/check/service.py
@@ -0,0 +1,60 @@
+# your_submodule/service.py
+
+import os
+import yaml
+from .testing.base import TestBase
+from .testing.test_example import ExampleTest # Import concrete test classes
+
+import logging
+
+log = logging.getLogger(__name__)
+
+
+def load_yaml_files(input_folder):
+ log.debug(f"Loading YAML files from {input_folder}")
+ yaml_files = [
+ f
+ for f in os.listdir(input_folder)
+ if f.endswith(".yaml") or f.endswith(".yml")
+ ]
+ rules = []
+ for file in yaml_files:
+ log.debug(f"Loading {file}")
+ try:
+ with open(os.path.join(str(input_folder), file), "r") as stream:
+ try:
+ data = yaml.safe_load(stream)
+ if data:
+ rules.extend(data)
+ except yaml.YAMLError as exc:
+ log.error(f"Error parsing {file}: {exc}")
+ except Exception as e:
+ log.exception(f"Error loading {file}: {e}")
+ log.debug(f"Loaded {len(rules)} rules")
+ return rules
+
+
+def instantiate_test(rule):
+ log.debug(f"Instantiating test from rule: {rule}")
+ test_type = rule.get("type")
+ log.debug(f"Test type: {test_type}")
+ if test_type == "example":
+ return ExampleTest(
+ url=rule.get("url"),
+ options=rule.get("options"),
+ type_test=rule.get("type"),
+ )
+ # Add more test types here
+ else:
+ raise ValueError(f"Unknown test type: {test_type}")
+
+
+def run_tests(input_folder):
+ rules = load_yaml_files(input_folder)
+ log.debug(f"Loaded rules: {rules}")
+ test_objects = [instantiate_test(rule) for rule in rules]
+ results = []
+ for test in test_objects:
+ result = test.run()
+ results.append(result)
+ return results
diff --git a/sema/check/sinks/__init__.py b/sema/check/sinks/__init__.py
new file mode 100644
index 0000000..0932f39
--- /dev/null
+++ b/sema/check/sinks/__init__.py
@@ -0,0 +1,11 @@
+# your_submodule/sinks/__init__.py
+
+from .csv_sink import write_csv
+from .html_sink import write_html
+from .yml_sink import write_yml
+
+__all__ = [
+ "write_csv",
+ "write_html",
+ "write_yml",
+]
diff --git a/sema/check/sinks/csv_sink.py b/sema/check/sinks/csv_sink.py
new file mode 100644
index 0000000..7d90d4b
--- /dev/null
+++ b/sema/check/sinks/csv_sink.py
@@ -0,0 +1,23 @@
+# your_submodule/sinks/csv_sink.py
+
+import csv
+from typing import List
+from ..testing.base import TestBase
+
+
+def write_csv(results: List[TestBase], output_file: str):
+ with open(output_file, "w", newline="") as csvfile:
+ fieldnames = ["url", "type", "success", "error", "message"]
+ writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
+
+ writer.writeheader()
+ for result in results:
+ writer.writerow(
+ {
+ "url": result.url,
+ "type": result.type_test,
+ "success": result.success,
+ "error": result.error,
+ "message": result.message,
+ }
+ )
diff --git a/sema/check/sinks/html_sink.py b/sema/check/sinks/html_sink.py
new file mode 100644
index 0000000..0842c60
--- /dev/null
+++ b/sema/check/sinks/html_sink.py
@@ -0,0 +1,19 @@
+# your_submodule/sinks/html_sink.py
+
+from typing import List
+from ..testing.base import TestResult
+
+
+def write_html(results: List[TestResult], output_file: str):
+ html_content = "Test Results"
+ html_content += "Success | Error | Message |
"
+ for result in results:
+ html_content += f"{result.success} | {result.error} | {result.message} |
"
+ html_content += "
"
+
+ with open(output_file, "w") as f:
+ f.write(html_content)
+
+
+# In the future add some way to visualise this with graphs or something
+# For now this is good enough
diff --git a/sema/check/sinks/yml_sink.py b/sema/check/sinks/yml_sink.py
new file mode 100644
index 0000000..3dc206f
--- /dev/null
+++ b/sema/check/sinks/yml_sink.py
@@ -0,0 +1,10 @@
+# your_submodule/sinks/yml_sink.py
+
+import yaml
+from typing import List
+from ..testing.base import TestResult
+
+
+def write_yml(results: List[TestResult], output_file: str):
+ with open(output_file, "w") as f:
+ yaml.dump([result.__dict__ for result in results], f)
diff --git a/sema/check/testing/base.py b/sema/check/testing/base.py
new file mode 100644
index 0000000..d2b5ec1
--- /dev/null
+++ b/sema/check/testing/base.py
@@ -0,0 +1,31 @@
+# your_submodule/tests/base.py
+
+from abc import ABC, abstractmethod
+from dataclasses import dataclass
+
+
+@dataclass
+class TestResult:
+ success: bool
+ error: bool
+ message: str
+ url: str
+ type_test: str
+
+
+class TestBase(ABC):
+ def __init__(self, url: str, type_test: str, options: dict):
+ self.url = url
+ self.type = type_test
+ self.options = options
+ self.result = TestResult(
+ success=False,
+ error=False,
+ message="",
+ url=url,
+ type_test=type_test,
+ )
+
+ @abstractmethod
+ def run(self) -> TestResult:
+ pass
diff --git a/sema/check/testing/test_example.py b/sema/check/testing/test_example.py
new file mode 100644
index 0000000..0e28608
--- /dev/null
+++ b/sema/check/testing/test_example.py
@@ -0,0 +1,42 @@
+# your_submodule/tests/test_example.py
+
+from .base import TestBase, TestResult
+
+
+class ExampleTest(TestBase):
+ def run(self) -> TestResult:
+ try:
+ # Example test logic
+ print(
+ f"Running example test on {self.url} with options {self.options}"
+ )
+ # Simulate success
+ self.result.url = self.url
+ self.result.type = self.type
+ self.result.success = True
+ self.result.message = "Example test passed."
+ except Exception as e:
+ self.result.error = True
+ self.result.message = str(e)
+ return self.result
+
+
+# More tests can be added here
+# TODO: Add sema-get tests :
+# 1. Test for successful response
+# 2. check for syntax errors in response
+# 3. Test for min-ammount of triples in response
+# 4. Test for specific triples in response with shacl validation
+
+# TODO: Add sema-conneg tests :
+# 1. Test for successful response
+# 2. check for all available MIME types in response and compare with expected
+# 3. check content disposition header for filename
+# 4. check contents for specific triples with shacl validation
+
+# TODO: Add sema-ldes tests :
+# 1. Test for successful response
+# 2. check if url can traverse to the next X pages
+# 3. check caching headers
+# 4. check for specific triples with shacl validation if LDES shape complies to
+# spec and if contents are valid and compliant to shape provided
diff --git a/sema/subyt/__main__.py b/sema/subyt/__main__.py
index 73de570..1b831b0 100644
--- a/sema/subyt/__main__.py
+++ b/sema/subyt/__main__.py
@@ -153,10 +153,10 @@ def _main(*args_list) -> bool:
subyt._sink.close() # TODO investigate suspicious location for this
-def main():
- success: bool = _main(*sys.argv[1:])
+def main(*cli_args):
+ success: bool = _main(cli_args)
sys.exit(0 if success else 1)
if __name__ == "__main__":
- main()
+ main(*sys.argv[1:])
diff --git a/tests/check/out/output.csv b/tests/check/out/output.csv
new file mode 100644
index 0000000..1568c14
--- /dev/null
+++ b/tests/check/out/output.csv
@@ -0,0 +1,3 @@
+url,type,success,error,message
+http://example.com,example,True,,Test passed
+http://example.com/2,example,False,Some error,Test failed
diff --git a/tests/check/out/output.yml b/tests/check/out/output.yml
new file mode 100644
index 0000000..202d52c
--- /dev/null
+++ b/tests/check/out/output.yml
@@ -0,0 +1,10 @@
+- error: null
+ message: Test passed
+ success: true
+ type_test: example
+ url: http://example.com
+- error: Some error
+ message: Test failed
+ success: false
+ type_test: example
+ url: http://example.com/2
diff --git a/tests/check/out/test_results.html b/tests/check/out/test_results.html
new file mode 100644
index 0000000..34ce551
--- /dev/null
+++ b/tests/check/out/test_results.html
@@ -0,0 +1 @@
+Test ResultsSuccess | Error | Message |
---|
True | False | Test 1 passed |
False | True | Test 2 failed |
\ No newline at end of file
diff --git a/tests/check/test_files/bad_formatted_yml.yml b/tests/check/test_files/bad_formatted_yml.yml
new file mode 100644
index 0000000..e69de29
diff --git a/tests/check/test_files/good_yaml.yml b/tests/check/test_files/good_yaml.yml
new file mode 100644
index 0000000..e2d296c
--- /dev/null
+++ b/tests/check/test_files/good_yaml.yml
@@ -0,0 +1,9 @@
+- url: "http://example.com"
+ type: "example"
+ options:
+ param1: "value1"
+ param2: "value2"
+- url: "http://another.com/another"
+ type: "example"
+ options:
+ paramA: "valueA"
diff --git a/tests/check/test_files/should not be processed.txt b/tests/check/test_files/should not be processed.txt
new file mode 100644
index 0000000..645eb31
--- /dev/null
+++ b/tests/check/test_files/should not be processed.txt
@@ -0,0 +1 @@
+should not be processed by sema-check
\ No newline at end of file
diff --git a/tests/check/test_sema_check_cli.py b/tests/check/test_sema_check_cli.py
new file mode 100644
index 0000000..a6d9413
--- /dev/null
+++ b/tests/check/test_sema_check_cli.py
@@ -0,0 +1,25 @@
+import sys
+import logging
+from pathlib import Path
+from sema.check.__main__ import main as query_main
+
+log = logging.getLogger(__name__)
+
+
+def test_main():
+ log.info(f"test_main_check")
+
+ input_folder = Path(__file__).parent / "test_files"
+ output_formats = ["csv", "html", "yml"]
+ for output in output_formats:
+ cli_line = f"--input_folder {input_folder} --output {output}"
+ # Backup the original sys.argv
+ original_argv = sys.argv
+ try:
+ # Set sys.argv to simulate command-line arguments
+ sys.argv = ["sema-check"] + cli_line.split()
+ success: bool = query_main(sys.argv)
+ # assert success, f"sema-check failed for output format: {output}"
+ finally:
+ # Restore the original sys.argv
+ sys.argv = original_argv
diff --git a/tests/check/test_service.py b/tests/check/test_service.py
new file mode 100644
index 0000000..d7c134c
--- /dev/null
+++ b/tests/check/test_service.py
@@ -0,0 +1,61 @@
+import sys
+import logging
+from pathlib import Path
+from sema.check import service
+
+log = logging.getLogger(__name__)
+
+
+def test_load_yaml_file():
+ input_folder = Path(__file__).parent / "test_files"
+
+ rules = [
+ {
+ "options": {"param1": "value1", "param2": "value2"},
+ "type": "example",
+ "url": "http://example.com",
+ },
+ {
+ "options": {"paramA": "valueA"},
+ "type": "example",
+ "url": "http://another.com/another",
+ },
+ ]
+ loaded_rules = service.load_yaml_files(input_folder)
+ assert len(loaded_rules) == 2
+ assert rules == loaded_rules
+
+
+def test_instantiate_tests():
+ rule = {
+ "options": {"param1": "value1", "param2": "value2"},
+ "type": "example",
+ "url": "http://example.com",
+ }
+ test = service.instantiate_test(rule)
+ assert test.url == "http://example.com"
+ assert test.options == {"param1": "value1", "param2": "value2"}
+ assert test.type == "example"
+
+
+def test_instantiate_unknown_type():
+ rule = {
+ "options": {"param1": "value1", "param2": "value2"},
+ "type": "unknown",
+ "url": "http://example.com",
+ }
+ try:
+ service.instantiate_test(rule)
+ assert False, "Should have raised a ValueError"
+ except ValueError as e:
+ assert str(e) == "Unknown test type: unknown"
+
+
+def test_run_tests():
+ input_folder = Path(__file__).parent / "test_files"
+ results = service.run_tests(input_folder)
+ assert len(results) == 2
+ assert results[0].success
+ assert results[1].success
+ assert not results[0].error
+ assert not results[1].error
diff --git a/tests/check/test_sinks.py b/tests/check/test_sinks.py
new file mode 100644
index 0000000..2739cb7
--- /dev/null
+++ b/tests/check/test_sinks.py
@@ -0,0 +1,130 @@
+import csv
+import os
+import yaml
+from pathlib import Path
+from sema.check.sinks import write_csv, write_html, write_yml
+from sema.check.testing.base import TestResult
+
+
+def test_write_csv():
+ # Arrange
+ results = [
+ TestResult(
+ success=True,
+ error=None,
+ message="Test passed",
+ url="http://example.com",
+ type_test="example",
+ ),
+ TestResult(
+ success=False,
+ error="Some error",
+ message="Test failed",
+ url="http://example.com/2",
+ type_test="example",
+ ),
+ ]
+ tmp_path = Path(__file__).parent / "out"
+ output_file = tmp_path / "output.csv"
+
+ # Act
+ write_csv(results, str(output_file))
+
+ # Assert
+ assert output_file.exists()
+ with open(output_file, newline="") as csvfile:
+ reader = csv.DictReader(csvfile)
+ rows = list(reader)
+ assert len(rows) == 2
+ assert rows[0]["success"] == "True"
+ assert rows[0]["error"] == ""
+ assert rows[0]["message"] == "Test passed"
+ assert rows[0]["url"] == "http://example.com"
+ assert rows[0]["type"] == "example"
+ assert rows[1]["success"] == "False"
+ assert rows[1]["error"] == "Some error"
+ assert rows[1]["message"] == "Test failed"
+ assert rows[1]["url"] == "http://example.com/2"
+ assert rows[1]["type"] == "example"
+
+
+def test_write_html():
+ results = [
+ TestResult(
+ success=True,
+ error=False,
+ message="Test 1 passed",
+ url="http://example.com",
+ type_test="example",
+ ),
+ TestResult(
+ success=False,
+ error=True,
+ message="Test 2 failed",
+ url="http://example.com/2",
+ type_test="example",
+ ),
+ ]
+ tmp_path = Path(__file__).parent / "out"
+ output_file = tmp_path / "test_results.html"
+
+ write_html(results, str(output_file))
+
+ assert output_file.exists()
+
+ with open(output_file, "r") as f:
+ content = f.read()
+ assert "" in content
+ assert "Test Results" in content
+ assert "" in content
+ assert (
+ "Success | Error | Message |
"
+ in content
+ )
+ assert (
+ "True | False | Test 1 passed |
"
+ in content
+ )
+ assert (
+ "False | True | Test 2 failed |
"
+ in content
+ )
+ assert "