{"id":37075761,"url":"https://github.com/foliant-docs/foliantcontrib.test_framework","last_synced_at":"2026-01-14T08:54:56.868Z","repository":{"id":57431775,"uuid":"308603194","full_name":"foliant-docs/foliantcontrib.test_framework","owner":"foliant-docs","description":null,"archived":false,"fork":false,"pushed_at":"2022-07-11T05:00:15.000Z","size":30,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-08-25T22:33:17.301Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/foliant-docs.png","metadata":{"files":{"readme":"README.md","changelog":"changelog.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-10-30T10:54:49.000Z","updated_at":"2022-07-10T08:43:46.000Z","dependencies_parsed_at":"2022-09-02T12:40:20.938Z","dependency_job_id":null,"html_url":"https://github.com/foliant-docs/foliantcontrib.test_framework","commit_stats":null,"previous_names":[],"tags_count":5,"template":false,"template_full_name":null,"purl":"pkg:github/foliant-docs/foliantcontrib.test_framework","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/foliant-docs%2Ffoliantcontrib.test_framework","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/foliant-docs%2Ffoliantcontrib.test_framework/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/foliant-docs%2Ffoliantcontrib.test_framework/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/foliant-docs%2Ffoliantcontrib.test_framework/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/foliant-docs","download_url":"https://codeload.github.com/foliant-docs/foliantcontrib.test_framework/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/foliant-docs%2Ffoliantcontrib.test_framework/sbom","scorecard":{"id":406108,"data":{"date":"2025-08-11","repo":{"name":"github.com/foliant-docs/foliantcontrib.test_framework","commit":"adbf83d5236f65061059f8e7a04d5da561cc8f9f"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":4.5,"checks":[{"name":"Code-Review","score":0,"reason":"Found 0/19 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Token-Permissions","score":10,"reason":"GitHub workflow tokens follow principle of least privilege","details":["Info: topLevel 'contents' permission set to 'read': .github/workflows/python-publish.yml:16","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Pinned-Dependencies","score":2,"reason":"dependency not pinned by hash detected -- score normalized to 2","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/python-publish.yml:23: update your workflow using https://app.stepsecurity.io/secureworkflow/foliant-docs/foliantcontrib.test_framework/python-publish.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/python-publish.yml:25: update your workflow using https://app.stepsecurity.io/secureworkflow/foliant-docs/foliantcontrib.test_framework/python-publish.yml/master?enable=pin","Warn: pipCommand not pinned by hash: .github/workflows/python-publish.yml:30","Warn: pipCommand not pinned by hash: .github/workflows/python-publish.yml:31","Info:   0 out of   2 GitHub-owned GitHubAction dependencies pinned","Info:   1 out of   1 third-party GitHubAction dependencies pinned","Info:   0 out of   2 pipCommand dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 3 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}}]},"last_synced_at":"2025-08-18T21:17:59.571Z","repository_id":57431775,"created_at":"2025-08-18T21:17:59.571Z","updated_at":"2025-08-18T21:17:59.571Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28414714,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-14T08:38:59.149Z","status":"ssl_error","status_checked_at":"2026-01-14T08:38:43.588Z","response_time":107,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-01-14T08:54:56.052Z","updated_at":"2026-01-14T08:54:56.842Z","avatar_url":"https://github.com/foliant-docs.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![](https://img.shields.io/pypi/v/foliantcontrib.test_framework.svg)](https://pypi.org/project/foliantcontrib.test_framework/) [![](https://img.shields.io/github/v/tag/foliant-docs/foliantcontrib.test_framework.svg?label=GitHub)](https://github.com/foliant-docs/foliantcontrib.test_framework)\n\n# Test framework\n\nFoliant test framework is a tool which helps you test your Foliant extensions.\nIt is still under development and right now you can only test preprocessors and config extensions with it\nusing Preprocessor Test Framework and Configuration Extensions Test Framework.\n\n## Preprocessor Test Framework\n\nPreprocessor Test Framework is a class which allows you to quickly set up a simulated environment for preprocessor testing.\nIt runs a specific preprocessor just like Foliant core runs it, and compares the results with expected output.\n\n### Usage\n\nFirst, you need to initialize the framework by passing it a name of preprocessor you want to test.\nLet's test the [includes](https://foliant-docs.github.io/docs/preprocessors/includes/) preprocessor in this example:\n\n```python\nfrom foliant_test.preprocessor import PreprocessorTestFramework\nptf = PreprocessorTestFramework('includes')\n```\n\nNow, to test the work of includes we need some source files. Source files are supplied in a mapping. We need to pass the framework both input source files and expected files.\n\nLet's create a basic file structure with just two files, one of which includes the other:\n\n```python\ninput_files = {\n'first.md': '# First file\\n\\n\u003cinclude src=\"second.md\"\u003e\u003c/include\u003e',\n'second.md': 'Second file content'\n}\n```\n\nNow let's create the expected mapping for these two files. What should be their contents after we apply the includes preprocessor?\n_The expected files must have the same names as the corresponding input files.\nOtherwise, an error will be thrown, even if the contents of the files are equal._\n\n```python\nexpected_files = {\n'first.md': '# First file\\n\\nSecond file content',\n'second.md': 'Second file content'\n}\n```\n\nAll is left to do is to run the test:\n\n```python\nptf.test_preprocessor(\ninput_mapping=input_files,\nexpected_mapping=expected_files\n)\n```\n\nIf you don't see any output, it means that everything went well and expected results were identical to the factual.\n\n#### Adding options\n\nTo set up your preprocessor options, change the `options` attribute of the framework instance.\n\nFor example, let's test the work of includes' `extensions` option, which allows us to process different file types besides `.md`\n\n```python\nptf.options = {'extensions': ['md', 'txt']}\ninput_files = {\n    'first.txt': '# First file\\n\\n\u003cinclude src=\"second.md\"\u003e\u003c/include\u003e',\n    'second.md': 'Second file content'\n}\nexpected_files = {\n    'first.txt': '# First file\\n\\nSecond file content',\n    'second.md': 'Second file content'\n}\nptf.test_preprocessor(\n    input_mapping=input_files,\n    expected_mapping=expected_files\n)\n```\n\nApart from options, you can also change:\n\n`config` attribute which represents the virtual `foliant.yml` dictionary;\n`chapters` attribute, which holds the list of chapters,\n`context` attribute which holds the whole preprocessor context.\n\n#### Helper Functions For Input And Expected Mappings\n\nIt's ok to keep contents of test files in strings right inside your test modules, but when these strings grow big it's more convenient to keep them in separate files. `preprocessor` module exports two functions which help you manage those:\n\n**unpack_file_dict**\n\n`unpack_file_dict` turns dictionary of filenames into dictionary of these files' contents.\n\n```python\nfrom foliant_test.preprocessor import unpack_file_dict\n\nfile_dict = {\n    'index.md': '/test_data/case1/index.md',  # paths should better be absolute\n    'description.md': '/test_data/case1/description.md',\n}\n```\n\nWhen you feed this dictionary to _unpack_file_dict_, it will replace paths to data files `'/test_data/case1/index.md'` \nand `'/test_data/case1/description.md'` with their contents:\n\n```python\n\u003e\u003e\u003e unpack_file_dict(file_dict)\n{\n    'index.md': 'index md contents',\n    'description.md': 'description md contents'\n}\n```\n\nSo you can pass the result straight to `input_mapping` or `expected_mapping` parameters:\n\n```python\nptf.test_preprocessor(\n    input_mapping=unpack_file_dict(file_dict),\n    expected_mapping=unpack_file_dict(expected_file_dict)\n)\n```\n\n**unpack_dir**\n\n`unpack_dir` creates the whole mapping for you, based on the contents of supplied dir. It reads all files inside specified dir and puts them into a dictionary: `{\u003cfile_name\u003e: \u003cfile_contents\u003e}`. Note: `\u003cfile_name\u003e` does not include a path, so `test_data/case1/index.md` will turn into `index.md`.\n\n```python\nfrom foliant_test.preprocessor import unpack_dir\n\ninput_dict = unpack_dir('/test_data/case1')  # paths should better be absolute\noutput_dict = unpack_dir('/test_data/case1_expected')\n```\n\n## Configuration Extension Test Framework\n\nConfiguration Extension Test Framework is a class which allows you to quickly set up simulated environment for testing config extensions. It parses the config just like Foliant core does it, and compares the results with expected output.\n\n### Usage\n\nFirst, you need to initialize the framework by passing it a name of extensions you want to test. Let's test the `path` builtin config extension in this example:\n\n```python\nfrom foliant_test.config_extension import ConfigExtensionTestFramework\nctf = ConfigExtensionTestFramework('path')\n```\n\nNow to test the work of `path` we need to supply a source config. The config is supplied in a YAML-string, as if it was a plain-text `foliant.yml` file.\n\nThe config string is supplied in `input_config` parameter, and is then compared to the expected config in `expected_config` parameter. Note that the latter is of type dict, because it's already parsed config.\n\n```python\nctf.test_extension(\n    input_config='mypath: !path README.md',\n    expected_config={'mypath': '/usr/src/app/myproject/README.md'}\n)\n\n```\n\nYou can adjust the following parameters before testing the extension:\n\n```python\nctf.project_path = Path('.')\nctf.config_file_name = '_foliant.yml'\nctf.quiet = True\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffoliant-docs%2Ffoliantcontrib.test_framework","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffoliant-docs%2Ffoliantcontrib.test_framework","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffoliant-docs%2Ffoliantcontrib.test_framework/lists"}