diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index cd3b897..d0d1627 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -39,3 +39,12 @@ jobs: - name: Build and run docker test image run: uv run inv test-app + + - name: Create libdoc + run: uv run inv docs + + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: SchemathesisLibrary-${{ matrix.python-version }}.html + path: docs/SchemathesisLibrary.html diff --git a/.gitignore b/.gitignore index fb010bb..4e0e3d4 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,9 @@ wheels/ # pytest .pytest_cache/ + +# Robot Framework +atest/output + +# VS Code +.vscode/ diff --git a/SchemathesisLibrary/__init__.py b/SchemathesisLibrary/__init__.py index 00c3739..255b95b 100644 --- a/SchemathesisLibrary/__init__.py +++ b/SchemathesisLibrary/__init__.py @@ -11,26 +11,79 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - +import schemathesis +from hypothesis import given, settings, HealthCheck, Phase, Verbosity from DataDriver import DataDriver # type: ignore +from robot.api.deco import keyword from robot.running.model import TestCase, TestSuite # type: ignore from robot.result.model import TestSuite as ResultTestSuite # type: ignore from robot.result.model import TestCase as ResultTestCase # type: ignore +from robotlibcore import DynamicCore # type: ignore +from DataDriver.AbstractReaderClass import AbstractReaderClass # type: ignore +from DataDriver.ReaderConfig import TestCaseData # type: ignore __version__ = "0.1.0" -class SchemathesisLibrary: +class SchemathesisReader(AbstractReaderClass): + def __init__(self, *, url: "str|None" = None): + self.url = url + + def get_data_from_source( + self, + ): # This method will be called from DataDriver to get the TestCaseData list. + schema = schemathesis.from_uri(self.url) + all_cases = [] + for op in schema.get_all_operations(): + op_as_strategy = op.ok().as_strategy() + for case in generate_examples(op_as_strategy, 10): + args = { + "${case}": case, + } + all_cases.append( + TestCaseData(test_case_name=str(case.id), arguments=args) + ) + return all_cases + + +class SchemathesisLibrary(DynamicCore): ROBOT_LIBRARY_VERSION = __version__ ROBOT_LISTENER_API_VERSION = 3 ROBOT_LIBRARY_SCOPE = "TEST SUITE" - def __init__(self): + def __init__(self, *, url: "str|None" = None): self.ROBOT_LIBRARY_LISTENER = self - self.data_driver = DataDriver() + self.data_driver = DataDriver( + reader_class=SchemathesisReader(url=url), file_regex="" + ) + DynamicCore.__init__(self, []) def _start_suite(self, data: TestSuite, result: ResultTestSuite): self.data_driver._start_suite(data, result) def _start_test(self, data: TestCase, result: ResultTestCase): self.data_driver._start_test(data, result) + + @keyword + def call_and_validate(self, case: schemathesis.Case) -> None: + """Validate a Schemathesis case.""" + case.call_and_validate() + + +def generate_examples(strategy, number) -> list[schemathesis.Case]: + examples = [] + + @given(strategy) + @settings( + database=None, + max_examples=number, + deadline=None, + verbosity=Verbosity.quiet, + phases=(Phase.generate,), + suppress_health_check=list(HealthCheck), + ) + def example_generating_inner_function(ex): + examples.append(ex) + + example_generating_inner_function() + return examples diff --git a/atest/test/all_cases.robot b/atest/test/all_cases.robot new file mode 100644 index 0000000..4179f9c --- /dev/null +++ b/atest/test/all_cases.robot @@ -0,0 +1,15 @@ +*** Settings *** +Library SchemathesisLibrary + +Test Template Wrapper + + +*** Test Cases *** +All Tests + Wrapper test_case_1 + + +*** Keywords *** +Wrapper + [Arguments] ${case} + Run And Validate ${case} diff --git a/pyproject.toml b/pyproject.toml index d20b337..bc72d41 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,6 +7,7 @@ requires-python = ">=3.10.1" dependencies = [ "robotframework>=7.2.2", "robotframework-datadriver>=1.11.2", + "robotframework-pythonlibcore>=4.4.1", "schemathesis>=3.39.16", ] diff --git a/tasks.py b/tasks.py index f6016d3..3f1e090 100644 --- a/tasks.py +++ b/tasks.py @@ -16,8 +16,10 @@ import requests from invoke.tasks import task +from robot.libdoc import libdoc -ROOT = Path(__file__).parent +ROOT_DIR = Path(__file__).parent +ATEST_OUTPUT_DIR = ROOT_DIR / "atest" / "output" DOCKER_IMAGE = "schemathesis-library-test" DOCKER_CONTAINER = "schemathesis-library-test-app" @@ -69,3 +71,38 @@ def test_app(ctx): print(f"Connection error: {error}") if i == 299: raise RuntimeError("Test app did not start in time") + + +@task +def docs(ctx, version: str | None = None): + """Generate library keyword documentation. + + Args: + version: Creates keyword documentation with version + suffix in the name. Documentation is moved to docs/vesions + folder. + """ + output = ROOT_DIR / "docs" / "SchemathesisLibrary.html" + libdoc("SchemathesisLibrary", str(output)) + if version is not None: + target = ( + ROOT_DIR / "docs" / "versions" / f"SchemathesisLibrary-{version.replace('v', '')}.html" + ) + output.rename(target) + + +@task +def atest(ctx): + """Run acceptance tests.""" + args = [ + "uv", + "run", + "robot", + "--pythonpath", + ".", + "--outputdir", + ATEST_OUTPUT_DIR.as_posix(), + "atest/test", + ] + ATEST_OUTPUT_DIR.mkdir(parents=True, exist_ok=True) + ctx.run(" ".join(args)) diff --git a/uv.lock b/uv.lock index b9b75fa..79fa406 100644 --- a/uv.lock +++ b/uv.lock @@ -1176,6 +1176,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/4c/fd/2d79ab59a6ebc8f5f6d20598dcf27a74bd378e05bc55a3f324a6c3c272d8/robotframework_datadriver-1.11.2-py3-none-any.whl", hash = "sha256:bb59c81834e58b2e7fa6bafc8180b2d659d2a09d13ecb7c6232da281e23804aa", size = 53070, upload-time = "2024-06-08T14:45:19.475Z" }, ] +[[package]] +name = "robotframework-pythonlibcore" +version = "4.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/71/89/5dc8c8186c897ee4b7d0b2631ebc90e679e8c8f04ea85505f96ad38aad64/robotframework-pythonlibcore-4.4.1.tar.gz", hash = "sha256:2d695b2ea906f5815179643e29182466675e682d82c5fa9d1009edfae2f84b16", size = 12835, upload-time = "2024-04-05T22:27:12.049Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ac/64/47d8403c7c0af89b46461640a2f67e49a5778062b8dd6eb3e128aa3c50cc/robotframework_pythonlibcore-4.4.1-py2.py3-none-any.whl", hash = "sha256:e0517129522aaa039eb2a28fd3d9720b7a0be0b90d0cbcb153a6c8016bb9e973", size = 12452, upload-time = "2024-04-05T22:27:10.353Z" }, +] + [[package]] name = "robotframework-schemathesis" version = "0.1.0" @@ -1183,6 +1192,7 @@ source = { virtual = "." } dependencies = [ { name = "robotframework" }, { name = "robotframework-datadriver" }, + { name = "robotframework-pythonlibcore" }, { name = "schemathesis" }, ] @@ -1201,6 +1211,7 @@ dev = [ requires-dist = [ { name = "robotframework", specifier = ">=7.2.2" }, { name = "robotframework-datadriver", specifier = ">=1.11.2" }, + { name = "robotframework-pythonlibcore", specifier = ">=4.4.1" }, { name = "schemathesis", specifier = ">=3.39.16" }, ]