diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..b5ac520 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,24 @@ +--- +name: Test jobs + +on: + - pull_request + - push + +jobs: + phpunit: + runs-on: ubuntu-20.04 + + strategy: + matrix: + php: ['7.3', '7.4'] + + steps: + - uses: actions/checkout@v2 + - uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + - uses: "ramsey/composer-install@v1" + with: + composer-options: --no-scripts + - run: bin/phpunit diff --git a/.github_changelog_generator b/.github_changelog_generator new file mode 100644 index 0000000..cd1f087 --- /dev/null +++ b/.github_changelog_generator @@ -0,0 +1,109 @@ +# ============================================================================== +# Project Specific +# ------------------------------------------------------------------------------ +user=pdsinterop +project=flysystem-rdf +# ------------------------------------------------------------------------------ +breaking-labels=backwards-incompatible, Backwards incompatible, breaking +bug-labels=bug +deprecated-labels=deprecated, Deprecated, Type: Deprecated +enhancement-labels=improvement, documentation, enhancement +exclude-labels=question, duplicate,invalid +removed-labels=removed, Removed, Type: Removed +security-labels=security, Security, Type: Security +summary-labels=Release summary, release-summary, Summary, summary +unreleased-label=Unreleased +# ============================================================================== + +# ============================================================================== +# Organisation-wide +# ------------------------------------------------------------------------------ +# Output and content related +# ------------------------------------------------------------------------------ +date-format=%Y-%m-%d +output=CHANGELOG.md +header=# Changelog +breaking-prefix=### Breaking changes +bug-prefix=### Fixes +deprecated-prefix=### Deprecates +enhancement-prefix=### Changes +issue-prefix=### Closes +merge-prefix=### Pull request(s) without label +removed-prefix=### Removes +security-prefix=### Security +# ------------------------------------------------------------------------------ +add-issues-wo-labels=true +add-pr-wo-labels=true +author=true +compare-link=true +filter-issues-by-milestone=true +# http-cache=true +issues=true +pulls=true +# unreleased-only=true +unreleased=true +usernames-as-github-logins=true +verbose=false +# ============================================================================== + +;user USER Username of the owner of the target GitHub repo OR the namespace of target Github repo if owned by an organization. +;project PROJECT Name of project on GitHub. +;token TOKEN To make more than 50 requests per hour your GitHub token is required. You can generate it at: https://github.com/settings/tokens/new +;date-format FORMAT Date format. Default is %Y-%m-%d. +;output NAME Output file. To print to STDOUT instead, use blank as path. Default is CHANGELOG.md +;base NAME Optional base file to append generated changes to. Default is HISTORY.md +;summary-label LABEL Set up custom label for the release summary section. Default is "". +;breaking-label LABEL Set up custom label for the breaking changes section. Default is "**Breaking changes:**". +;enhancement-label LABEL Set up custom label for enhancements section. Default is "**Implemented enhancements:**". +;bugs-label LABEL Set up custom label for bug-fixes section. Default is "**Fixed bugs:**". +;deprecated-label LABEL Set up custom label for the deprecated changes section. Default is "**Deprecated:**". +;removed-label LABEL Set up custom label for the removed changes section. Default is "**Removed:**". +;security-label LABEL Set up custom label for the security changes section. Default is "**Security fixes:**". +;issues-label LABEL Set up custom label for closed-issues section. Default is "**Closed issues:**". +;header-label LABEL Set up custom header label. Default is "# Changelog". +;configure-sections STRING Define your own set of sections which overrides all default sections. +;add-sections HASH, STRING Add new sections but keep the default sections. +;front-matter JSON Add YAML front matter. Formatted as JSON because it's easier to add on the command line. +;pr-label LABEL Set up custom label for pull requests section. Default is "**Merged pull requests:**". +;issues Include closed issues in changelog. Default is true. +;issues-wo-labels Include closed issues without labels in changelog. Default is true. +;pr-wo-labels Include pull requests without labels in changelog. Default is true. +;pull-requests Include pull-requests in changelog. Default is true. +;filter-by-milestone Use milestone to detect when issue was resolved. Default is true. +;issues-of-open-milestones Include issues of open milestones. Default is true. +;author Add author of pull request at the end. Default is true. +;usernames-as-github-logins Use GitHub tags instead of Markdown links for the author of an issue or pull-request. +;unreleased-only Generate log from unreleased closed issues only. +;unreleased Add to log unreleased closed issues. Default is true. +;unreleased-label LABEL Set up custom label for unreleased closed issues section. Default is "**Unreleased:**". +;compare-link Include compare link (Full Changelog) between older version and newer version. Default is true. +;include-labels x,y,z Of the labeled issues, only include the ones with the specified labels. +;exclude-labels x,y,z Issues with the specified labels will be excluded from changelog. Default is 'duplicate,question,invalid,wontfix'. +;summary-labels x,y,z Issues with these labels will be added to a new section, called "Release Summary". The section display only body of issues. Default is 'release-summary,summary'. +;breaking-labels x,y,z Issues with these labels will be added to a new section, called "Breaking changes". Default is 'backwards-incompatible,breaking'. +;enhancement-labels x,y,z Issues with the specified labels will be added to "Implemented enhancements" section. Default is 'enhancement,Enhancement'. +;bug-labels x,y,z Issues with the specified labels will be added to "Fixed bugs" section. Default is 'bug,Bug'. +;deprecated-labels x,y,z Issues with the specified labels will be added to a section called "Deprecated". Default is 'deprecated,Deprecated'. +;removed-labels x,y,z Issues with the specified labels will be added to a section called "Removed". Default is 'removed,Removed'. +;security-labels x,y,z Issues with the specified labels will be added to a section called "Security fixes". Default is 'security,Security'. +;issue-line-labels x,y,z The specified labels will be shown in brackets next to each matching issue. Use "ALL" to show all labels. Default is []. +;include-tags-regex REGEX Apply a regular expression on tag names so that they can be included, for example: --include-tags-regex ".*+d{1,}". +;exclude-tags x,y,z Changelog will exclude specified tags +;exclude-tags-regex REGEX Apply a regular expression on tag names so that they can be excluded, for example: --exclude-tags-regex ".*+d{1,}". +;since-tag x Changelog will start after specified tag. +;due-tag x Changelog will end before specified tag. +;since-commit x Fetch only commits after this time. eg. "2017-01-01 10:00:00" +;max-issues NUMBER Maximum number of issues to fetch from GitHub. Default is unlimited. +;release-url URL The URL to point to for release links, in printf format (with the tag as variable). +;github-site URL The Enterprise GitHub site where your project is hosted. +;github-api URL The enterprise endpoint to use for your GitHub API. +;simple-list Create a simple list from issues and pull requests. Default is false. +;future-release VERSION Put the unreleased changes in the specified release number. +;release-branch BRANCH Limit pull requests to the release branch, such as master or release. +;http-cache Use HTTP Cache to cache GitHub API requests (useful for large repos). Default is true. +;cache-file CACHE-FILE Filename to use for cache. Default is github-changelog-http-cache in a temporary directory. +;cache-log CACHE-LOG Filename to use for cache log. Default is github-changelog-logger.log in a temporary directory. +;config-file CONFIG-FILE Path to configuration file. Default is .github_changelog_generator. +;ssl-ca-file PATH Path to cacert.pem file. Default is a bundled lib/github_changelog_generator/ssl_certs/cacert.pem. Respects SSL_CA_PATH. +;require x,y,z Path to Ruby file(s) to require before generating changelog. +;verbose Run verbosely. Default is true. diff --git a/.gitignore b/.gitignore index 3af7618..265830f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ # Directories to ignore +/.phpunit.cache +/build /vendor # Files to ignore diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..6a1cd43 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,33 @@ +# Changelog + +## [v0.3.0](https://github.com/pdsinterop/flysystem-rdf/tree/v0.3.0) (2020-11-09) + +[Full Changelog](https://github.com/pdsinterop/flysystem-rdf/compare/v0.2.1...v0.3.0) + +## [v0.2.1](https://github.com/pdsinterop/flysystem-rdf/tree/v0.2.1) (2020-11-02) + +[Full Changelog](https://github.com/pdsinterop/flysystem-rdf/compare/v0.2.0...v0.2.1) + +### Closes + +- Cherry-pick from php5-librdf? [\#1](https://github.com/pdsinterop/flysystem-rdf/issues/1) + +## [v0.2.0](https://github.com/pdsinterop/flysystem-rdf/tree/v0.2.0) (2020-09-24) + +[Full Changelog](https://github.com/pdsinterop/flysystem-rdf/compare/v0.1.0...v0.2.0) + +### Closes + +- Errors in foaf.rdf [\#2](https://github.com/pdsinterop/flysystem-rdf/issues/2) + +### Pull request(s) without label + +- Take base URL for parsing [\#3](https://github.com/pdsinterop/flysystem-rdf/pull/3) (@michielbdejong) + +## [v0.1.0](https://github.com/pdsinterop/flysystem-rdf/tree/v0.1.0) (2020-07-19) + +[Full Changelog](https://github.com/pdsinterop/flysystem-rdf/compare/v0.0.0...v0.1.0) + +## [v0.0.0](https://github.com/pdsinterop/flysystem-rdf/tree/v0.0.0) (2020-07-19) + +[Full Changelog](https://github.com/pdsinterop/flysystem-rdf/compare/3afc40850b2e61d3a7fc0005f7c3ae48aebbcceb...v0.0.0) diff --git a/README.md b/README.md index 4dc5135..65323d4 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,13 @@ # Flysystem RDF Converter Plugin - -[![PDS Interop][pdsinterop-shield]][pdsinterop-site] [![Project stage: Development][project-stage-badge: Development]][project-stage-page] [![License][license-shield]][license-link] [![Latest Version][version-shield]][version-link] -[![standard-readme compliant][standard-readme-shield]][standard-readme-link] ![Maintained][maintained-shield] +[![PDS Interop][pdsinterop-shield]][pdsinterop-site] +[![standard-readme compliant][standard-readme-shield]][standard-readme-link] +[![keep-a-changelog compliant][keep-a-changelog-shield]][keep-a-changelog-link] + _Flysystem plugin to transform RDF data between various serialization formats._ When using RDF, you will notice there are several different popular @@ -52,11 +53,11 @@ separate package. The advised install method is through composer: -``` +```sh composer require pdsinterop/flysystem-rdf ``` -PHP version 7.1 and higher is supported. The [`mbstring`](https://www.php.net/manual/en/book.mbstring.php) +PHP version 7.3 and higher is supported. The [`mbstring`](https://www.php.net/manual/en/book.mbstring.php) extension needs to be enabled in order for this package to work. ## Usage @@ -118,10 +119,16 @@ $convertedMimeType = $filesystem // This also works for `has` $hasConvertedContents = $filesystem ->asMime('text/turtle') - ->has('/foaf.rdf'); + ->has('/foaf.ttl'); + +// Without using the plugin, this will be false +$hasContents = $filesystem->has('/foaf.ttl'); ``` +The adapter adds any found `.meta` files that reference a file to that file's +metadata, which can be retrieved with `$filesystem->getMetadata($path)`. + ### Plugin To use the plugin, instantiate it and add it to a Flysystem filesystem. @@ -141,8 +148,19 @@ $filesystem->addPlugin($plugin); // Read the contents of a RDF file in another format from what was stored in $content = $filesystem->readRdf('/foaf.rdf', \Pdsinterop\Rdf\Enum\Format::TURTLE); + ``` +## Develop + +- Do not forget to install the required dependencies using `composer`. + +- Most of the logic here involves EasyRdf and/or FlySystem. You'll want to familiarise yourself with their workings. + +- Test are available in the `tests/` directory. They are run by GitHub actions for any pull request. To run them,call `./bin/phpunit` + +- The changelog can be automatically generated using [`github_changelog_generator`](https://github.com/github-changelog-generator/github-changelog-generator) with the [provided configuration file](.github_changelog_generator) + ## Contribute Questions or feedback can be given by [opening an issue on GitHub](https://github.com/pdsinterop/flysystem-rdf/issues). @@ -163,14 +181,16 @@ For a list of changes see the [CHANGELOG](CHANGELOG.md) or the GitHub releases p All code created by PDS Interop is licensed under the [MIT License][license-link]. [contributors-page]: https://github.com/pdsinterop/flysystem-rdf/contributors +[keep-a-changelog-link]: https://keepachangelog.com/ +[keep-a-changelog-shield]: https://img.shields.io/badge/Keep%20a%20Changelog-f15d30.svg?logo=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGw9IiNmZmYiIHZpZXdCb3g9IjAgMCAxODcgMTg1Ij48cGF0aCBkPSJNNjIgN2MtMTUgMy0yOCAxMC0zNyAyMmExMjIgMTIyIDAgMDAtMTggOTEgNzQgNzQgMCAwMDE2IDM4YzYgOSAxNCAxNSAyNCAxOGE4OSA4OSAwIDAwMjQgNCA0NSA0NSAwIDAwNiAwbDMtMSAxMy0xYTE1OCAxNTggMCAwMDU1LTE3IDYzIDYzIDAgMDAzNS01MiAzNCAzNCAwIDAwLTEtNWMtMy0xOC05LTMzLTE5LTQ3LTEyLTE3LTI0LTI4LTM4LTM3QTg1IDg1IDAgMDA2MiA3em0zMCA4YzIwIDQgMzggMTQgNTMgMzEgMTcgMTggMjYgMzcgMjkgNTh2MTJjLTMgMTctMTMgMzAtMjggMzhhMTU1IDE1NSAwIDAxLTUzIDE2bC0xMyAyaC0xYTUxIDUxIDAgMDEtMTItMWwtMTctMmMtMTMtNC0yMy0xMi0yOS0yNy01LTEyLTgtMjQtOC0zOWExMzMgMTMzIDAgMDE4LTUwYzUtMTMgMTEtMjYgMjYtMzMgMTQtNyAyOS05IDQ1LTV6TTQwIDQ1YTk0IDk0IDAgMDAtMTcgNTQgNzUgNzUgMCAwMDYgMzJjOCAxOSAyMiAzMSA0MiAzMiAyMSAyIDQxLTIgNjAtMTRhNjAgNjAgMCAwMDIxLTE5IDUzIDUzIDAgMDA5LTI5YzAtMTYtOC0zMy0yMy01MWE0NyA0NyAwIDAwLTUtNWMtMjMtMjAtNDUtMjYtNjctMTgtMTIgNC0yMCA5LTI2IDE4em0xMDggNzZhNTAgNTAgMCAwMS0yMSAyMmMtMTcgOS0zMiAxMy00OCAxMy0xMSAwLTIxLTMtMzAtOS01LTMtOS05LTEzLTE2YTgxIDgxIDAgMDEtNi0zMiA5NCA5NCAwIDAxOC0zNSA5MCA5MCAwIDAxNi0xMmwxLTJjNS05IDEzLTEzIDIzLTE2IDE2LTUgMzItMyA1MCA5IDEzIDggMjMgMjAgMzAgMzYgNyAxNSA3IDI5IDAgNDJ6bS00My03M2MtMTctOC0zMy02LTQ2IDUtMTAgOC0xNiAyMC0xOSAzN2E1NCA1NCAwIDAwNSAzNGM3IDE1IDIwIDIzIDM3IDIyIDIyLTEgMzgtOSA0OC0yNGE0MSA0MSAwIDAwOC0yNCA0MyA0MyAwIDAwLTEtMTJjLTYtMTgtMTYtMzEtMzItMzh6bS0yMyA5MWgtMWMtNyAwLTE0LTItMjEtN2EyNyAyNyAwIDAxLTEwLTEzIDU3IDU3IDAgMDEtNC0yMCA2MyA2MyAwIDAxNi0yNWM1LTEyIDEyLTE5IDI0LTIxIDktMyAxOC0yIDI3IDIgMTQgNiAyMyAxOCAyNyAzM3MtMiAzMS0xNiA0MGMtMTEgOC0yMSAxMS0zMiAxMXptMS0zNHYxNGgtOFY2OGg4djI4bDEwLTEwaDExbC0xNCAxNSAxNyAxOEg5NnoiLz48L3N2Zz4K [license-link]: ./LICENSE [license-shield]: https://img.shields.io/github/license/pdsinterop/flysystem-rdf.svg -[maintained-shield]: https://img.shields.io/maintenance/yes/2020 -[pdsinterop-shield]: https://img.shields.io/badge/-PDS%20Interop-gray.svg?logo=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9Ii01IC01IDExMCAxMTAiIGZpbGw9IiNGRkYiIHN0cm9rZS13aWR0aD0iMCI+CiAgICA8cGF0aCBkPSJNLTEgNTJoMTdhMzcuNSAzNC41IDAgMDAyNS41IDMxLjE1di0xMy43NWEyMC43NSAyMSAwIDAxOC41LTQwLjI1IDIwLjc1IDIxIDAgMDE4LjUgNDAuMjV2MTMuNzVhMzcgMzQuNSAwIDAwMjUuNS0zMS4xNWgxN2EyMiAyMS4xNSAwIDAxLTEwMiAweiIvPgogICAgPHBhdGggZD0iTSAxMDEgNDhhMi43NyAyLjY3IDAgMDAtMTAyIDBoIDE3YTIuOTcgMi44IDAgMDE2OCAweiIvPgo8L3N2Zz4K +[maintained-shield]: https://img.shields.io/maintenance/yes/2022.svg +[pdsinterop-shield]: https://img.shields.io/badge/-PDS%20Interop-7C4DFF.svg?logo=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9Ii01IC01IDExMCAxMTAiIGZpbGw9IiNGRkYiIHN0cm9rZS13aWR0aD0iMCI+CiAgICA8cGF0aCBkPSJNLTEgNTJoMTdhMzcuNSAzNC41IDAgMDAyNS41IDMxLjE1di0xMy43NWEyMC43NSAyMSAwIDAxOC41LTQwLjI1IDIwLjc1IDIxIDAgMDE4LjUgNDAuMjV2MTMuNzVhMzcgMzQuNSAwIDAwMjUuNS0zMS4xNWgxN2EyMiAyMS4xNSAwIDAxLTEwMiAweiIvPgogICAgPHBhdGggZD0iTSAxMDEgNDhhMi43NyAyLjY3IDAgMDAtMTAyIDBoIDE3YTIuOTcgMi44IDAgMDE2OCAweiIvPgo8L3N2Zz4K [pdsinterop-site]: https://pdsinterop.org/ [project-stage-badge: Development]: https://img.shields.io/badge/Project%20Stage-Development-yellowgreen.svg [project-stage-page]: https://blog.pother.ca/project-stages/ [standard-readme-link]: https://github.com/RichardLitt/standard-readme -[standard-readme-shield]: https://img.shields.io/badge/readme%20style-standard-brightgreen.svg +[standard-readme-shield]: https://img.shields.io/badge/-Standard%20Readme-brightgreen.svg [version-link]: https://packagist.org/packages/pdsinterop/flysystem-rdf [version-shield]: https://img.shields.io/github/v/release/pdsinterop/flysystem-rdf?sort=semver diff --git a/composer.json b/composer.json index efd3ab1..bcf189d 100644 --- a/composer.json +++ b/composer.json @@ -5,25 +5,21 @@ } }, "config": { - "sort-packages": true, "bin-dir": "./bin", - "platform": { - "php": "7.1.33", - "ext-mbstring": "0.0.0" - } + "sort-packages": true }, "description": "Flysystem plugin to transform RDF data between various serialization formats.", "license": "MIT", "name": "pdsinterop/flysystem-rdf", "require": { - "php": "^7.1", + "php": "^7.3", "ext-mbstring": "*", "easyrdf/easyrdf": "^0.9.1", "league/flysystem": "^1.0", "ml/json-ld": "^1.2" }, - "type": "library", "require-dev": { - "phpunit/phpunit": "^7|^8|^9" - } + "phpunit/phpunit": "^8|^9" + }, + "type": "library" } diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 0000000..6c43fed --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,49 @@ + + + + + tests/ + + + + + + + src/ + + + + + + + + + + + + + src/ + + + + + + + + + + diff --git a/src/Enum/Format.php b/src/Enum/Format.php index ebd3431..e4fb2b3 100644 --- a/src/Enum/Format.php +++ b/src/Enum/Format.php @@ -11,7 +11,7 @@ class Format public const TURTLE = 'turtle'; public const UNKNOWN = 'guess'; - final public static function has($value) : bool + final public static function has($value): bool { return in_array($value, self::keys(), true); } @@ -21,7 +21,7 @@ final public static function has($value) : bool * * @return string[] */ - final public static function keys() : array + final public static function keys(): array { $keys = (new \ReflectionClass(__CLASS__))->getConstants(); diff --git a/src/Flysystem/Adapter/Rdf.php b/src/Flysystem/Adapter/Rdf.php index cbe3ade..e72bc01 100644 --- a/src/Flysystem/Adapter/Rdf.php +++ b/src/Flysystem/Adapter/Rdf.php @@ -2,52 +2,73 @@ namespace Pdsinterop\Rdf\Flysystem\Adapter; -use EasyRdf_Exception; -use EasyRdf_Graph; +use EasyRdf_Exception as RdfException; +use EasyRdf_Graph as Graph; use League\Flysystem\AdapterInterface; use League\Flysystem\Config; +use ML\JsonLD\JsonLD; use Pdsinterop\Rdf\Enum\Format; use Pdsinterop\Rdf\Flysystem\Exception; -use Pdsinterop\Rdf\Formats; -use ML\JsonLD; +use Pdsinterop\Rdf\FormatsInterface; + /** * Filesystem adapter to convert RDF files to and from a default format */ -class Rdf implements AdapterInterface +class Rdf implements RdfAdapterInterface { - private const ERROR_COULD_NOT_CONVERT = 'Could not convert file "%s" to format "%s": %s'; + ////////////////////////////// CLASS PROPERTIES \\\\\\\\\\\\\\\\\\\\\\\\\\\\ + + public const ERROR_UNSUPPORTED_FORMAT = 'Given format "%s" is not supported'; + public const ERROR_COULD_NOT_CONVERT = 'Could not convert file "%s" to format "%s": %s'; /** @var AdapterInterface */ private $adapter; /** @var string */ private $format = ''; - /** @var Formats */ + /** @var FormatsInterface */ private $formats; + /** @var Graph */ + private $graph; /** @var string */ private $url; - final public function setFormat(string $format) : void + //////////////////////////// GETTERS AND SETTERS \\\\\\\\\\\\\\\\\\\\\\\\\\\ + + /** + * Retrieve a new / clean RDF Graph object + * + * @return Graph + */ + private function getGraph(): Graph + { + return clone $this->graph; + } + + final public function setFormat(string $format): void { - if (($format != "") && (Format::has($format) === false)) { - throw new Exception('Given format "' . $format . '" is not supported'); + if (($format !== "") && (Format::has($format) === false)) { + throw Exception::create(self::ERROR_UNSUPPORTED_FORMAT, [$format]); } $this->format = $format; } - final public function getFormat() { + + final public function getFormat(): string + { return $this->format; } - // FIXME: remove easyrdf graph from the constructor - final public function __construct(AdapterInterface $adapter, EasyRdf_Graph $graph, Formats $formats, string $url) + //////////////////////////////// PUBLIC API \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ + + // @FIXME: Add JsonLD as dependency and use static calls to object instance instead of using static calls to class + final public function __construct(AdapterInterface $adapter, Graph $graph, FormatsInterface $formats, string $url) { $this->adapter = $adapter; $this->formats = $formats; + $this->graph = $graph; $this->url = $url; } - // ========================================================================= - final public function write($path, $contents, Config $config) { return call_user_func_array([$this->adapter, __FUNCTION__], func_get_args()); @@ -100,20 +121,28 @@ final public function setVisibility($path, $visibility) final public function has($path) { - return call_user_func_array([$this->adapter, __FUNCTION__], func_get_args()); + return $this->getMetadata($path); } final public function read($path) { - $format = $this->resetFormat(); - return $format !== '' - ? [ - 'type' => 'file', + $format = $this->format; + + if ($format !== '') { + $contents = $this->convertedContents($path, $format); + + $metaData = [ + 'contents' => $contents, + 'mimetype' => $this->formats->getMimeForFormat($format), 'path' => $path, - 'contents' => $this->convertedContents($path, $format), - ] - : call_user_func_array([$this->adapter, __FUNCTION__], func_get_args()) - ; + 'size' => strlen($contents), // filesize in bytes, + 'type' => 'file', + ]; + } else { + $metaData = $this->adapter->read($path); + } + + return $metaData; } final public function readStream($path) @@ -129,30 +158,48 @@ final public function listContents($directory = '', $recursive = false) final public function getMetadata($path) { - return call_user_func_array([$this->adapter, __FUNCTION__], func_get_args()); + $metadata = $this->adapter->getMetadata($path) ?? []; + + $format = $this->format; + + if ($format !== '') { + // @CHECKME: Does it make more sense to call `guessMimeType` or should `getMimeType` be called? + $metadata = array_merge($metadata, ['mimetype' => $this->guessMimeType($path)], $this->read($path)); + } + + return array_merge($metadata, $this->findAuxiliaryResources($path)); } final public function getSize($path) { - // @TODO: For convert request, get contents, convert and count size - return call_user_func_array([$this->adapter, __FUNCTION__], func_get_args()); + $format = $this->format; + + if ($format === '') { + $metadata = call_user_func_array([$this->adapter, __FUNCTION__], func_get_args()); + } else { + $metadata = $this->getMetadata($path); + } + + return $metadata; } final public function getMimeType($path) { $format = $this->resetFormat(); - $extension = $this->getExtension($path); - $possibleFormat = $this->formats->getFormatForExtension($extension); - - $mimeType = $this->adapter->getMimetype($path); if ($format !== '') { - $mimeType['mimetype'] = $this->formats->getMimeForFormat($format); - } elseif ($possibleFormat !== '' && $mimeType['mimetype'] === 'text/plain') { - $mimeType['mimetype'] = $possibleFormat; + $metadata = ['mimetype' => $this->formats->getMimeForFormat($format)]; + } else { + $metadata = $this->adapter->getMimetype($path); + + $possibleMimeType = $this->guessMimeType($path, $metadata); + + if ($possibleMimeType !== '') { + $metadata['mimetype'] = $possibleMimeType; + } } - return $mimeType; + return $metadata; } final public function getTimestamp($path) @@ -165,7 +212,7 @@ final public function getVisibility($path) return call_user_func_array([$this->adapter, __FUNCTION__], func_get_args()); } - // ========================================================================= + ////////////////////////////// UTILITY METHODS \\\\\\\\\\\\\\\\\\\\\\\\\\\\\ private function convertedContents($path, $format) { @@ -173,14 +220,14 @@ private function convertedContents($path, $format) $originalContents = $this->getOriginalContents($path); $originalFormat = $this->formats->getFormatForExtension($originalExtension); - if ($originalFormat == $format) { + if ($originalFormat === $format) { return $originalContents; } try { switch($originalFormat) { case "jsonld": - $graph = new \EasyRdf_Graph(); + $graph = $this->getGraph(); // FIXME: parsing json gives warnings, so we're suppressing those for now. $graph->parse($originalContents, "jsonld", $this->url); switch ($format) { @@ -193,15 +240,16 @@ private function convertedContents($path, $format) } break; default: - $graph = new \EasyRdf_Graph(); + $graph = $this->getGraph(); + // FIXME: guessing here helps pass another test, but we really should provide a correct format. // FIXME: parsing json gives warnings, so we're suppressing those for now. - @$graph->parse($originalContents, "guess", $this->url); // FIXME: guessing here helps pass another test, but we really should provide a correct format. + @$graph->parse($originalContents, "guess", $this->url); switch ($format) { case "jsonld": // We need to get the expanded version of the json-ld, but easyRdf doesn't provide an option for that, so we call this directly. $contents = $graph->serialise($format); - $jsonDoc = \ML\JsonLD\JsonLD::expand($contents); - $contents = \ML\JsonLD\JsonLD::toString($jsonDoc); + $jsonDoc = JsonLD::expand($contents); + $contents = JsonLD::toString($jsonDoc); break; default: $contents = $graph->serialise($format); @@ -209,8 +257,8 @@ private function convertedContents($path, $format) } break; } - } catch (EasyRdf_Exception $exception) { - throw new Exception(self::ERROR_COULD_NOT_CONVERT, [ + } catch (RdfException $exception) { + throw Exception::create(self::ERROR_COULD_NOT_CONVERT, [ 'file' => $path, 'format' => $format, 'error' => $exception->getMessage(), @@ -220,7 +268,41 @@ private function convertedContents($path, $format) return $contents; } - private function getExtension(string $path) : string + private function findAuxiliaryResources(string $path): array + { + $metaFiles = [ + 'describedby' => $this->findInPath($path, '.meta'), + 'acl' => $this->findInPath($path, '.acl'), + ]; + + // Remove any empty values + return array_filter($metaFiles); + } + + private function findInPath(string $originalPath, $extension) + { + $subject = false; + + $subjectPath = $originalPath . $extension; + + if ($this->adapter->has($subjectPath)) { + $subject = $subjectPath; + } else { + do { + $subjectPath = dirname($subjectPath); + + $path = '/' . ltrim($subjectPath . '/' . $extension, '/'); + + if ($this->adapter->has($path)) { + $subject = $path; + } + } while ($subject === false && $subjectPath !== '/'); + } + + return $subject; + } + + private function getExtension(string $path): string { return strtolower(pathinfo($path, PATHINFO_EXTENSION)); } @@ -232,6 +314,33 @@ private function getOriginalContents($path) return $converted['contents']; } + private function guessMimeType(string $path, array $metadata = []): string + { + $mimetype = ''; + + if ($metadata === []) { + $originalMetadata = $this->adapter->getMimetype($path); + if (isset($originalMetadata['mimetype'])) { + $metadata = $originalMetadata; + } + } + + $extension = $this->getExtension($path); + + $possibleMime = $this->formats->getMimeForExtension($extension); + + if ($possibleMime !== '' + && ( + ! isset($metadata['mimetype']) + || $metadata['mimetype'] === 'text/plain' + ) + ) { + $mimetype = $possibleMime; + } + + return $mimetype; + } + private function resetFormat() : string { $format = $this->format; diff --git a/src/Flysystem/Adapter/RdfAdapterInterface.php b/src/Flysystem/Adapter/RdfAdapterInterface.php new file mode 100644 index 0000000..b6baaf2 --- /dev/null +++ b/src/Flysystem/Adapter/RdfAdapterInterface.php @@ -0,0 +1,15 @@ +formats = $formats; } - public function handle(string $mime) : FilesystemInterface + public function handle(string $mime): FilesystemInterface { $filesystem = $this->filesystem; - $adapter = $filesystem->getAdapter(); - + if (! is_callable([$filesystem, 'getAdapter'])) { + throw Exception::create(self::ERROR_MISSING_ADAPTER, [__CLASS__]); + } - if ($adapter instanceof Rdf) { + if ($filesystem->getAdapter() instanceof RdfAdapterInterface) { $format = $this->formats->getFormatForMime($mime); - $adapter->setFormat($format); + $filesystem->getAdapter()->setFormat($format); } return $filesystem; diff --git a/src/Flysystem/Plugin/ReadRdf.php b/src/Flysystem/Plugin/ReadRdf.php index 28dbaa7..438e0aa 100644 --- a/src/Flysystem/Plugin/ReadRdf.php +++ b/src/Flysystem/Plugin/ReadRdf.php @@ -48,12 +48,12 @@ final public function __construct(EasyRdf_Graph $rdfConverter) * @param string $format RDF format to convert file to * @param string $url base url for parsing * - * @return array|false metadata + * @return string|false converted contents * * @throws FileNotFoundException * @throws Exception */ - public function handle(string $path, string $format, string $url) : string + public function handle(string $path, string $format, string $url) { $converter = $this->converter; @@ -65,7 +65,7 @@ public function handle(string $path, string $format, string $url) : string try { $converter->parse($contents, Format::UNKNOWN, $url); } catch (EasyRdf_Exception $exception) { - $this->throwException(self::ERROR_COULD_NOT_CONVERT, [ + throw Exception::create(self::ERROR_COULD_NOT_CONVERT, [ 'file' => $path, 'format' => $format, 'error' => $exception->getMessage(), @@ -83,16 +83,4 @@ public function handle(string $path, string $format, string $url) : string return $contents; } - - /** - * @param string $error - * @param array $context - * @param \Exception|null $previous - * - * @throws Exception - */ - private function throwException(string $error, array $context, \Exception $previous = null) : void - { - throw new Exception(vsprintf($error, $context), 0, $previous); - } } diff --git a/src/Formats.php b/src/Formats.php index 787ca60..2ab4d73 100644 --- a/src/Formats.php +++ b/src/Formats.php @@ -4,7 +4,7 @@ use Pdsinterop\Rdf\Enum\Format; -class Formats +class Formats implements FormatsInterface { ////////////////////////////// CLASS PROPERTIES \\\\\\\\\\\\\\\\\\\\\\\\\\\\ @@ -55,12 +55,12 @@ class Formats //////////////////////////// GETTERS AND SETTERS \\\\\\\\\\\\\\\\\\\\\\\\\\\ - final public function getAllExtensions() : array + final public function getAllExtensions(): array { return $this->getAll('extensions'); } - final public function getAllMimeTypes() : array + final public function getAllMimeTypes(): array { $all = $this->getAll('mimeTypes'); @@ -69,19 +69,19 @@ final public function getAllMimeTypes() : array }, $all); } - private function getData() : array + private function getData(): array { return $this->data; } //////////////////////////////// PUBLIC API \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ - final public function getExtensionsForFormat(string $format) : array + final public function getExtensionsForFormat(string $format): array { return $this->getAllExtensions()[$format] ?? []; } - final public function getFormatForExtension(string $extension) : string + final public function getFormatForExtension(string $extension): string { $extension = ltrim($extension, '.'); @@ -96,7 +96,7 @@ final public function getFormatForExtension(string $extension) : string return reset($formatNames); } - final public function getFormatForMime(string $mime) : string + final public function getFormatForMime(string $mime): string { $all = $this->getAllMimeTypes(); @@ -109,27 +109,28 @@ final public function getFormatForMime(string $mime) : string return reset($formatNames); } - final public function getMimeForFormat(string $format) + final public function getMimeForExtension(string $extension): string { + $format = $this->getFormatForExtension($extension); + return $this->getMimeForFormat($format); + } + + final public function getMimeForFormat(string $format): string + { $mimes = $this->getMimesForFormat($format); return reset($mimes); } - final public function getMimesForFormat(string $format) : array + final public function getMimesForFormat(string $format): array { return $this->getAllMimeTypes()[$format] ?? []; } ////////////////////////////// UTILITY METHODS \\\\\\\\\\\\\\\\\\\\\\\\\\\\\ - /** - * @param string $subject - * - * @return array - */ - private function getAll(string $subject) : array + private function getAll(string $subject): array { $data = $this->getData(); diff --git a/src/FormatsInterface.php b/src/FormatsInterface.php new file mode 100644 index 0000000..fd2c5b6 --- /dev/null +++ b/src/FormatsInterface.php @@ -0,0 +1,22 @@ + $format): ?> - id="tab-" name="tabs" type="radio" class="tab-selector is-hidden" /> +

-
readRdf('/foaf.rdf', $format)); ?>
+
readRdf('/foaf.rdf', $format, \Pdsinterop\Rdf\Enum\Rdf::EMPTY_NODE)) ?>
diff --git a/tests/integration/FilesystemTest.php b/tests/integration/FilesystemTest.php index 19d2004..308b08a 100644 --- a/tests/integration/FilesystemTest.php +++ b/tests/integration/FilesystemTest.php @@ -17,8 +17,10 @@ class FilesystemTest extends TestCase * @param $expected * * @dataProvider provideFormatResult + * + * @coversNothing */ - public function test_($format, $expected) : void + public function test_($format, $expected): void { $filesystem = new Filesystem(new Local(__DIR__ . '/../fixtures/')); $filesystem->addPlugin(new ReadRdf(new EasyRdf_Graph())); @@ -29,7 +31,7 @@ public function test_($format, $expected) : void self::assertEquals($expected, $actual); } - public function provideFormatResult() : array + public function provideFormatResult(): array { return [ 'json-ld' => [ diff --git a/tests/unit/Enum/FormatTest.php b/tests/unit/Enum/FormatTest.php new file mode 100644 index 0000000..a5a9768 --- /dev/null +++ b/tests/unit/Enum/FormatTest.php @@ -0,0 +1,72 @@ +assertTrue($actual); + } + + /** + * @testdox Enum should return `false` when asked if it has a `guess` format + * + * @covers ::has + * + * @uses \Pdsinterop\Rdf\Enum\Format::keys + */ + public function testHasForGuess(): void + { + $actual = Format::has('guess'); + + $this->assertFalse($actual); + } + + /** + * @testdox Enum should return supported formats when asked for all is keys + * + * @covers ::keys + */ + public function testKeys(): void + { + $actual = Format::keys(); + + $expected = [ + 'JSON_LD' => 'jsonld', + 'N_TRIPLES' => 'ntriples', + 'NOTATION_3' => 'n3', + 'RDF_XML' => 'rdfxml', + 'TURTLE' => 'turtle', + ]; + + $this->assertEquals($expected, $actual); + } + + public function provideFormats(): array + { + return [ + 'jsonld' => ['jsonld'], + 'ntriples' => ['ntriples'], + 'n3' => ['n3'], + 'rdfxml' => ['rdfxml'], + 'turtle' => ['turtle'], + ]; + } +} diff --git a/tests/unit/Flysystem/Adapter/RdfTest.php b/tests/unit/Flysystem/Adapter/RdfTest.php new file mode 100644 index 0000000..3d8c21d --- /dev/null +++ b/tests/unit/Flysystem/Adapter/RdfTest.php @@ -0,0 +1,627 @@ + + * + * @TODO: All long test names should be replaced by a short name and a @testdox annotation + */ +class RdfTest extends TestCase +{ + ////////////////////////////////// FIXTURES \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ + + private const MOCK_CONTENTS = 'mock contents'; + private const MOCK_CONTENTS_RDF = "@prefix rdfs: <> .\n rdfs:comment '' ."; + private const MOCK_MIME = 'mock mime'; + private const MOCK_PATH = '/mock/path'; + private const MOCK_URL = 'mock url'; + + /** @var AdapterInterface|MockObject */ + private $mockAdapter; + /** @var Formats|MockObject */ + private $mockFormats; + /** @var Graph|MockObject */ + private $mockGraph; + + private function createAdapter(): Rdf + { + $this->mockAdapter = $this->mockAdapter ?? $this->getMockBuilder(AdapterInterface::class)->getMock(); + $this->mockGraph = $this->getMockBuilder(Graph::class)->getMock(); + $this->mockFormats = $this->getMockBuilder(FormatsInterface::class)->getMock(); + + return new Rdf($this->mockAdapter, $this->mockGraph, $this->mockFormats, self::MOCK_URL); + } + + ////////////////////////// TESTS WITHOUT FORMATTING \\\\\\\\\\\\\\\\\\\\\\\\ + + /** + * @covers ::__construct + */ + public function testRdfAdapterShouldComplainWhenInstantiatedWithoutAdapter(): void + { + $this->expectException(ArgumentCountError::class); + $this->expectExceptionMessage('0 passed'); + + new Rdf(); + } + + /** + * @covers ::__construct + */ + public function testRdfAdapterShouldComplainWhenInstantiatedWithoutGraph(): void + { + $this->expectException(ArgumentCountError::class); + $this->expectExceptionMessage('1 passed'); + + $mockAdapter = $this->getMockBuilder(AdapterInterface::class) + ->getMock() + ; + + new Rdf($mockAdapter); + } + + /** + * @covers ::__construct + */ + public function testRdfAdapterShouldComplainWhenInstantiatedWithoutFormats(): void + { + $this->expectException(ArgumentCountError::class); + $this->expectExceptionMessage('2 passed'); + + $mockAdapter = $this->getMockBuilder(AdapterInterface::class)->getMock(); + $mockGraph = $this->getMockBuilder(Graph::class)->getMock(); + + new Rdf($mockAdapter, $mockGraph); + } + + /** + * @covers ::__construct + */ + public function testRdfAdapterShouldComplainWhenInstantiatedWithoutUrl(): void + { + $this->expectException(ArgumentCountError::class); + $this->expectExceptionMessage('3 passed'); + + $mockAdapter = $this->getMockBuilder(AdapterInterface::class)->getMock(); + $mockGraph = $this->getMockBuilder(Graph::class)->getMock(); + $mockFormats = $this->getMockBuilder(Formats::class)->getMock(); + + new Rdf($mockAdapter, $mockGraph, $mockFormats); + } + + /** + * @covers ::__construct + */ + public function testRdfAdapterShouldBeInstantiatedWhenGivenExpectedDependencies(): void + { + $this->assertInstanceOf(Rdf::class, $this->createAdapter()); + } + + /** + * @covers ::copy + * @covers ::createDir + * @covers ::delete + * @covers ::getSize + * @covers ::deleteDir + * @covers ::getMetadata + * @covers ::getTimestamp + * @covers ::getVisibility + * @covers ::listContents + * @covers ::read + * @covers ::readStream + * @covers ::rename + * @covers ::setVisibility + * @covers ::update + * @covers ::updateStream + * @covers ::write + * @covers ::writeStream + * + * @uses \Pdsinterop\Rdf\Flysystem\Adapter\Rdf::getMimeType + * + * @dataProvider provideProxyMethods + */ + public function testRdfAdapterShouldReturnInnerAdapterResultWhenProxyMethodsAreCalled($method, $parameters): void + { + $expected = self::MOCK_CONTENTS; + + $adapterMethod = $method; + + if ($method === 'read' || $method === 'readStream') { + $adapterMethod = 'read'; + $expected = ['contents' => $expected]; + } elseif ($method === 'getMimetype') { + $expected = ['mimetype' => self::MOCK_MIME]; + } elseif ($method === 'getMetadata') { + $expected = []; + } + + $adapter = $this->createAdapter(); + + if ($method === 'getMetadata' || $method === 'read' || $method === 'readStream') { + $this->mockAdapter + ->method('read') + ->willReturn(['contents' => self::MOCK_CONTENTS]) + ; + } + + $this->mockAdapter + ->method('has') + ->willReturn(false) + ; + + $this->mockAdapter->expects($this->once()) + ->method($adapterMethod) + ->willReturn($expected) + ; + + $actual = $adapter->{$method}(...$parameters); + + $this->assertSame($expected, $actual); + } + + //////////////////////////// TESTS WITH FORMATTING \\\\\\\\\\\\\\\\\\\\\\\\\ + + /** + * @covers ::setFormat + * + * @uses \Pdsinterop\Rdf\Enum\Format + * @uses \Pdsinterop\Rdf\Flysystem\Exception + * + * @dataProvider provideUnsupportedFormats + */ + public function testRdfAdapterShouldComplainWhenAskedToSetUnsupportedFormat($format): void + { + $adapter = $this->createAdapter(); + $message = vsprintf($adapter::ERROR_UNSUPPORTED_FORMAT, [$format]); + + $this->expectException(Exception::class); + $this->expectExceptionMessage($message); + + $adapter->setFormat($format); + } + + /** + * @covers ::getFormat + * @covers ::setFormat + * + * @uses \Pdsinterop\Rdf\Enum\Format + * + * @dataProvider provideSupportedFormats + */ + public function testRdfAdapterShouldSetFormatWhenAskedToSetSupportedFormat($expected): void + { + $adapter = $this->createAdapter(); + + $adapter->setFormat($expected); + + $actual = $adapter->getFormat(); + + $this->assertSame($expected, $actual); + } + + /** + * @covers ::getMimeType + * @covers ::getSize + * @covers ::has + * @covers ::read + * @covers ::readStream + * + * @uses \Pdsinterop\Rdf\Enum\Format + * @uses \Pdsinterop\Rdf\Flysystem\Adapter\Rdf::getMetadata + * @uses \Pdsinterop\Rdf\Flysystem\Adapter\Rdf::setFormat + * @uses \Pdsinterop\Rdf\Formats + * + * @dataProvider provideConvertingMethods + */ + public function testRdfAdapterShouldCallInnerAdapterAndGraphWhenNonProxyMethodsAreCalledWithFormat($method): void + { + $formats = [ + Format::JSON_LD, + Format::N_TRIPLES, + Format::NOTATION_3, + Format::RDF_XML, + Format::TURTLE, + ]; + + $formatCount = count($formats); + + $adapterMethod = $method; + + $adapter = $this->createAdapter(); + + if ($method === 'readStream') { + /*/ The `readStream` method is currently just a proxy for `read` /*/ + $adapterMethod = 'read'; + } + + $this->mockAdapter->method('read') + ->willReturn(['contents' => self::MOCK_CONTENTS_RDF]) + ; + + $this->mockGraph->method('serialise') + ->willReturn('[]') + ; + + $this->mockFormats->method('getMimeForFormat') + ->willReturn(self::MOCK_MIME) + ; + + if ($method === 'getMimeType' || $method === 'getSize') { + /*/ These inner adapter method should *never* be called when working with converted (meta)data /*/ + $this->mockAdapter->expects($this->never()) + ->method($adapterMethod) + ; + } elseif ($method === 'has') { + $this->mockAdapter + ->method($adapterMethod) + ->willReturn(false) + ; + } elseif ($method === 'getMetadata' || $method === 'read' || $method === 'readStream') { + $this->mockAdapter->expects($this->exactly($formatCount)) + ->method($adapterMethod) + ; + } else { + $this->fail('Do not know how to test for ' . $method); + } + + $expected = [ + 'contents' => '[]', + 'mimetype' => self::MOCK_MIME, + 'path' => self::MOCK_PATH, + 'size' => 2, + 'type' => 'file', + ]; + + if ($method === 'getMimeType') { + /*/ Mimetype does not require metadata or read to function. + Hence, it only returns one value. + /*/ + $expected = ['mimetype' => self::MOCK_MIME]; + } + + foreach ($formats as $format) { + $adapter->setFormat($format); + + $actual = $adapter->{$method}(self::MOCK_PATH); + + $this->assertEquals($expected, $actual); + } + } + + //////////////////////////// TESTS FOR METADATA \\\\\\\\\\\\\\\\\\\\\\\\\\\\ + + /** + * @covers ::getMetadata + */ + public function testMetaDataShouldNotContainDescribedByWhenCalledForPathWithoutMetaFile(): void + { + $adapter = $this->createAdapter(); + + $this->mockAdapter->method('has')->willReturn(false); + + $actual = $adapter->getMetadata(self::MOCK_PATH); + + $this->assertArrayNotHasKey('describedby', $actual); + } + + /** + * @covers ::getMetadata + * + * @uses \Pdsinterop\Rdf\Enum\Format + * @uses \Pdsinterop\Rdf\Flysystem\Adapter\Rdf::read + * @uses \Pdsinterop\Rdf\Flysystem\Adapter\Rdf::setFormat + */ + public function testMetaDataShouldLookForMetaFileWhenCalled(): void + { + $adapter = $this->createAdapter(); + + /*/ This part of the test is only needed for conversion /*/ + $adapter->setFormat('jsonld'); + + $this->mockAdapter + ->method('read') + ->willReturn(['contents' => self::MOCK_CONTENTS]) + ; + + /*/ This part is always needed /*/ + $path = self::MOCK_PATH; + + $this->mockAdapter + ->method('has') + ->willReturn(true) + ; + + $actual = $adapter->getMetadata($path); + + $this->assertArrayHasKey('describedby', $actual); + } + + /** + * @covers ::getMetadata + * + * @uses \Pdsinterop\Rdf\Enum\Format + * @uses \Pdsinterop\Rdf\Flysystem\Adapter\Rdf::read + * @uses \Pdsinterop\Rdf\Flysystem\Adapter\Rdf::setFormat + */ + public function testMetaDataShouldLookForMetaFileInDirectoryWhenCalledOnFileWithoutMetaFile(): void + { + $adapter = $this->createAdapter(); + + /*/ This part of the test is only needed for conversion /*/ + $adapter->setFormat('jsonld'); + + $this->mockAdapter + ->method('read') + ->willReturn(['contents' => self::MOCK_CONTENTS]) + ; + + /*/ This part is always needed /*/ + $expected = '/a/longer/path/to/.meta'; + + + $this->mockAdapter->expects($this->exactly(3)) + ->method('has') + ->withConsecutive( + ['/a/longer/path/to/file.ext.meta'], + [$expected], + ) + ->willReturnOnConsecutiveCalls(false, true, true) + ; + + $metadata = $adapter->getMetadata('/a/longer/path/to/file.ext'); + + $actual = $metadata['describedby']; + + $this->assertEquals($expected, $actual); + } + + /** + * @covers ::getMetadata + * + * @uses \Pdsinterop\Rdf\Enum\Format + * @uses \Pdsinterop\Rdf\Flysystem\Adapter\Rdf::read + * @uses \Pdsinterop\Rdf\Flysystem\Adapter\Rdf::setFormat + */ + public function testMetaDataShouldLookForMetaFileInParentDirectoriesWhenCalledOnFileWithoutMetaFileInCurrentDirectory(): void + { + $adapter = $this->createAdapter(); + + /*/ This part of the test is only needed for conversion /*/ + $adapter->setFormat('jsonld'); + + $this->mockAdapter + ->method('read') + ->willReturn(['contents' => self::MOCK_CONTENTS]) + ; + + /*/ This part is always needed /*/ + $expected = '/.meta'; + + $this->mockAdapter + ->method('has') + ->withConsecutive( + ['/a/longer/path/to/file.ext.meta'], + ['/a/longer/path/to/.meta'], + ['/a/longer/path/.meta'], + ['/a/longer/.meta'], + ['/a/.meta'], + ['/.meta'], + ) + ->willReturnOnConsecutiveCalls(false, false, false, false, false, true, true) + ; + + $metadata = $adapter->getMetadata('/a/longer/path/to/file.ext'); + + $actual = $metadata['describedby']; + + $this->assertEquals($expected, $actual); + } + + /////////////////////////////// TESTS FOR ACL \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ + + /** + * @covers ::getMetadata + */ + public function testMetaDataShouldNotContainAclWhenCalledForPathWithoutAclFile(): void + { + $adapter = $this->createAdapter(); + + $this->mockAdapter->method('has')->willReturn(false); + + $actual = $adapter->getMetadata(self::MOCK_PATH); + + $this->assertArrayNotHasKey('acl', $actual); + } + + /** + * @covers ::getMetadata + * + * @uses \Pdsinterop\Rdf\Enum\Format + * @uses \Pdsinterop\Rdf\Flysystem\Adapter\Rdf::read + * @uses \Pdsinterop\Rdf\Flysystem\Adapter\Rdf::setFormat + */ + public function testMetaDataShouldLookForAclFileWhenCalled(): void + { + $adapter = $this->createAdapter(); + + /*/ This part of the test is only needed for conversion /*/ + $adapter->setFormat('jsonld'); + + $this->mockAdapter + ->method('read') + ->willReturn(['contents' => self::MOCK_CONTENTS]) + ; + + /*/ This part is always needed /*/ + $path = self::MOCK_PATH; + + $this->mockAdapter + ->method('has') + ->willReturn(true) + ; + + $actual = $adapter->getMetadata($path); + + $this->assertArrayHasKey('acl', $actual); + } + + /** + * @covers ::getMetadata + * + * @uses \Pdsinterop\Rdf\Enum\Format + * @uses \Pdsinterop\Rdf\Flysystem\Adapter\Rdf::read + * @uses \Pdsinterop\Rdf\Flysystem\Adapter\Rdf::setFormat + */ + public function testMetaDataShouldLookForAclFileInDirectoryWhenCalledOnFileWithoutAclFile(): void + { + $adapter = $this->createAdapter(); + + /*/ This part of the test is only needed for conversion /*/ + $adapter->setFormat('jsonld'); + + $this->mockAdapter + ->method('read') + ->willReturn(['contents' => self::MOCK_CONTENTS]) + ; + + /*/ This part is always needed /*/ + $expected = '/a/longer/path/to/.acl'; + + + $this->mockAdapter->expects($this->exactly(3)) + ->method('has') + ->withConsecutive( + ['/a/longer/path/to/file.ext.meta'], + ['/a/longer/path/to/file.ext.acl'], + [$expected], + ) + ->willReturnOnConsecutiveCalls(true, false, true) + ; + + $metadata = $adapter->getMetadata('/a/longer/path/to/file.ext'); + + $actual = $metadata['acl']; + + $this->assertEquals($expected, $actual); + } + + /** + * @covers ::getMetadata + * + * @uses \Pdsinterop\Rdf\Enum\Format + * @uses \Pdsinterop\Rdf\Flysystem\Adapter\Rdf::read + * @uses \Pdsinterop\Rdf\Flysystem\Adapter\Rdf::setFormat + */ + public function testMetaDataShouldLookForAclFileInParentDirectoriesWhenCalledOnFileWithoutAclFileInCurrentDirectory(): void + { + $adapter = $this->createAdapter(); + + /*/ This part of the test is only needed for conversion /*/ + $adapter->setFormat('jsonld'); + + $this->mockAdapter + ->method('read') + ->willReturn(['contents' => self::MOCK_CONTENTS]) + ; + + /*/ This part is always needed /*/ + $expected = '/.acl'; + + $this->mockAdapter + ->method('has') + ->withConsecutive( + ['/a/longer/path/to/file.ext.meta'], + ['/a/longer/path/to/file.ext.acl'], + ['/a/longer/path/to/.acl'], + ['/a/longer/path/.acl'], + ['/a/longer/.acl'], + ['/a/.acl'], + [$expected], + ) + ->willReturnOnConsecutiveCalls(true, false, false, false, false, false, true) + ; + + $metadata = $adapter->getMetadata('/a/longer/path/to/file.ext'); + + $actual = $metadata['acl']; + + $this->assertEquals($expected, $actual); + } + + /////////////////////////////// DATAPROVIDERS \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ + + public function provideProxyMethods(): array + { + $mockConfig = $this->getMockBuilder(Config::class)->getMock(); + $mockContents = self::MOCK_CONTENTS; + $mockPath = self::MOCK_PATH; + $mockResource = fopen('php://temp', 'rb'); + + return [ + 'copy' => ['copy', [$mockPath, $mockPath]], + 'createDir' => ['createDir', [$mockPath, $mockConfig]], + 'delete' => ['delete', [$mockPath]], + 'deleteDir' => ['deleteDir', [$mockPath]], + 'getMetadata' => ['getMetadata', [$mockPath]], + 'getMimetype' => ['getMimetype', [$mockPath]], + 'getSize' => ['getSize', [$mockPath]], + 'getVisibility' => ['getVisibility', [$mockPath]], + 'getTimestamp' => ['getTimestamp', [$mockPath]], + 'listContents' => ['listContents', []], + 'read' => ['read', [$mockPath]], + 'readStream' => ['readStream', [$mockPath]], + 'rename' => ['rename', [$mockPath, $mockPath]], + 'setVisibility' => ['setVisibility', [$mockPath, 'mock visibility']], + 'update' => ['update', [$mockPath, $mockContents, $mockConfig]], + 'updateStream' => ['updateStream', [$mockPath, $mockResource, $mockConfig]], + 'write' => ['write', [$mockPath, $mockContents, $mockConfig]], + 'writeStream' => ['writeStream', [$mockPath, $mockResource, $mockConfig]], + ]; + } + + public function provideConvertingMethods(): array + { + return [ + 'getMetadata' => ['getMetadata'], + 'getSize' => ['getSize'], + 'has' => ['has'], + 'getMimeType' => ['getMimeType'], + 'read' => ['read'], + 'readStream' => ['readStream'], + ]; + } + + public function provideSupportedFormats(): array + { + return [ + 'string: empty' => [''], + Format::JSON_LD => [Format::JSON_LD], + Format::N_TRIPLES => [Format::N_TRIPLES], + Format::NOTATION_3 => [Format::NOTATION_3], + Format::RDF_XML => [Format::RDF_XML], + Format::TURTLE => [Format::TURTLE], + ]; + } + + public function provideUnsupportedFormats(): array + { + return [ + 'mock format' => ['mock format'], + Format::UNKNOWN => [Format::UNKNOWN], + ]; + } +} diff --git a/tests/unit/Flysystem/Plugin/AsMimeTest.php b/tests/unit/Flysystem/Plugin/AsMimeTest.php new file mode 100644 index 0000000..6eef03a --- /dev/null +++ b/tests/unit/Flysystem/Plugin/AsMimeTest.php @@ -0,0 +1,239 @@ + + */ +class AsMimeTest extends TestCase +{ + ////////////////////////////////// FIXTURES \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ + + private const MOCK_MIME = 'mock mime'; + + private function createPlugin() : AsMime + { + $mockFormats = $this->createMockFormats(); + + $mockFilesystem = $this->createMockFilesystem(); + + $plugin = new AsMime($mockFormats); + + $plugin->setFilesystem($mockFilesystem); + + return $plugin; + } + + ////////////////////////////// CUSTOM ASSERTS \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ + private function assertPropertyEquals($object, string $property, $expected): void + { + $reflector = new ReflectionObject($object); + + $attribute = $reflector->getProperty($property); + $attribute->setAccessible(true); + + $actual = $attribute->getValue($object); + + $this->assertSame($expected, $actual); + } + + /////////////////////////////////// TESTS \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ + + /** + * @covers ::__construct + */ + public function testPluginShouldComplainWhenInstantiatedWithoutGraph(): void + { + $this->expectException(ArgumentCountError::class); + + new AsMime(); + } + + /** + * @covers ::__construct + */ + public function testPluginShouldReceiveEasyRdfGraphWhenInstantiated(): void + { + $mockFormats = $this->createMockFormats(); + + $actual = new AsMime($mockFormats); + + $this->assertInstanceOf(AsMime::class, $actual); + } + + /** + * @covers ::setFilesystem + */ + public function testPluginShouldComplainWhenSetFilesystemCalledWithoutFilesystem(): void + { + $mockFormats = $this->createMockFormats(); + + $plugin = new AsMime($mockFormats); + + $this->expectException(ArgumentCountError::class); + + $plugin->setFilesystem(); + } + + /** + * @covers ::setFilesystem + */ + public function testPluginShouldContainFilesystemWhenFilesystemGiven(): void + { + $mockFormats = $this->createMockFormats(); + + $plugin = new AsMime($mockFormats); + + $expected = $this->createMockFilesystem(); + + $plugin->setFilesystem($expected); + + $this->assertPropertyEquals($plugin, 'filesystem', $expected); + } + + /** + * @covers ::getMethod + */ + public function testPluginShouldReturnExpectedMethodNameWhenAskedForMethod(): void + { + $mockFormats = $this->createMockFormats(); + + $plugin = new AsMime($mockFormats); + + $expected = 'asMime'; + + $actual = $plugin->getMethod(); + + $this->assertEquals($expected, $actual); + } + + /** + * @covers ::handle + * + * @uses \Pdsinterop\Rdf\Flysystem\Exception::create + */ + public function testPluginShouldComplainWhenHandleCalledWithoutFilesystem(): void + { + $mockFormats = $this->createMockFormats(); + + $plugin = new AsMime($mockFormats); + + $this->expectException(Exception::class); + $this->expectExceptionMessage('can not be used before an adapter has been added to the filesystem'); + + $plugin->handle(''); + } + + /** + * @covers ::handle + */ + public function testPluginShouldComplainWhenHandleCalledWithoutMimetype(): void + { + $mockFormats = $this->createMockFormats(); + + $plugin = new AsMime($mockFormats); + + $this->expectException(ArgumentCountError::class); + $this->expectExceptionMessage('Too few arguments to function Pdsinterop\Rdf\Flysystem\Plugin\AsMime::handle(), 0 passed'); + + $plugin->handle(); + } + + /** + * @covers ::handle + */ + public function testPluginShouldReturnFileSystemWhenHandleCalled(): void + { + $expected = $this->createMockFilesystem(); + + $mockFormats = $this->createMockFormats(); + + $plugin = new AsMime($mockFormats); + $plugin->setFilesystem($expected); + + $this->assertPropertyEquals($plugin, 'filesystem', $expected); + + $actual = $plugin->handle(self::MOCK_MIME); + + $this->assertSame($expected, $actual); + } + + /** + * @covers ::handle + */ + public function testPluginShouldNotSetFormatWhenAdapterIsNotRdfAdapter(): void + { + $plugin = $this->createPlugin(); + + $this->createMockFormats()->expects($this->never()) + ->method('getFormatForMime') + ; + + $plugin->handle(self::MOCK_MIME); + } + + /** + * @covers ::handle + */ + public function testPluginShouldSetFormatWhenAdapterIsRdfAdapter(): void + { + $mockFormats = $this->createMockFormats(); + + $mockAdapter = $this->getMockBuilder(RdfAdapterInterface::class)->getMock(); + + $mockAdapter->expects($this->once()) + ->method('setFormat') + ; + + $mockFilesystem = $this->createMockFilesystem($mockAdapter); + + $plugin = new AsMime($mockFormats); + $plugin->setFilesystem($mockFilesystem); + + $plugin->handle(self::MOCK_MIME); + } + + ////////////////////////////// MOCKS AND STUBS \\\\\\\\\\\\\\\\\\\\\\\\\\\\\ + + /** + * @return FilesystemInterface | MockObject + */ + private function createMockFilesystem($mockAdapter = null): FilesystemInterface + { + $mockFilesystem = $this->getMockBuilder(Filesystem::class) + ->disableOriginalConstructor() + ->getMock(); + + if ($mockAdapter === null) { + $mockAdapter = $this->getMockBuilder(AdapterInterface::class)->getMock(); + } + + $mockFilesystem->method('getAdapter') + ->willReturn($mockAdapter) + ; + + return $mockFilesystem; + } + + /** + * @return FormatsInterface | MockObject + */ + private function createMockFormats() + { + return $this->getMockBuilder(FormatsInterface::class) + ->getMock(); + } +} diff --git a/tests/unit/ReadRdfTest.php b/tests/unit/Flysystem/Plugin/ReadRdfTest.php similarity index 54% rename from tests/unit/ReadRdfTest.php rename to tests/unit/Flysystem/Plugin/ReadRdfTest.php index 469a01d..3120b1a 100644 --- a/tests/unit/ReadRdfTest.php +++ b/tests/unit/Flysystem/Plugin/ReadRdfTest.php @@ -3,19 +3,36 @@ namespace Pdsinterop\Rdf\Flysystem\Plugin; use ArgumentCountError; +use EasyRdf_Exception; use EasyRdf_Graph; use Error; +use League\Flysystem\FileNotFoundException; use League\Flysystem\FilesystemInterface; use Pdsinterop\Rdf\Enum\Format; +use Pdsinterop\Rdf\Flysystem\Exception; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use ReflectionObject; +/** + * @coversDefaultClass \Pdsinterop\Rdf\Flysystem\Plugin\ReadRdf + * @covers ::__construct + * @covers :: + * + * @TODO: All long test names should be replaced by a short name and a @testdox annotation + */ class ReadRdfTest extends TestCase { + ////////////////////////////////// FIXTURES \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ + + private const MOCK_CONTENTS = 'mock contents'; + private const MOCK_FORMAT = 'mock format'; + private const MOCK_PATH = 'mock path'; + private const MOCK_URL = 'mock url'; + ////////////////////////////// CUSTOM ASSERTS \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ - private static function assertPropertyEquals($object, string $property, $expected) : void + private static function assertPropertyEquals($object, string $property, $expected): void { $reflector = new ReflectionObject($object); @@ -30,15 +47,22 @@ private static function assertPropertyEquals($object, string $property, $expecte /////////////////////////////////// TESTS \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ - public function testRdfPluginShouldComplainWhenInstantiatedWithoutGraph() : void + /** + * @covers ::__construct + */ + public function testRdfPluginShouldComplainWhenInstantiatedWithoutGraph(): void { $this->expectException(ArgumentCountError::class); /** @noinspection PhpParamsInspection */ + /** @noinspection PhpExpressionResultUnusedInspection */ new ReadRdf(); } - public function testRdfPluginShouldReceiveEasyRdfGraphWhenInstantiated() : void + /** + * @covers ::__construct + */ + public function testRdfPluginShouldReceiveEasyRdfGraphWhenInstantiated(): void { $mockGraph = $this->getMockEasyRdfGraph(); @@ -47,7 +71,10 @@ public function testRdfPluginShouldReceiveEasyRdfGraphWhenInstantiated() : void self::assertInstanceOf(ReadRdf::class, $actual); } - public function testRdfPluginShouldComplainWhenSetFilesystemCalledWithoutFilesystem() : void + /** + * @covers ::setFilesystem + */ + public function testRdfPluginShouldComplainWhenSetFilesystemCalledWithoutFilesystem(): void { $mockGraph = $this->getMockEasyRdfGraph(); @@ -59,7 +86,10 @@ public function testRdfPluginShouldComplainWhenSetFilesystemCalledWithoutFilesys $plugin->setFilesystem(); } - public function testRdfPluginShouldContainFilesystemWhenFilesystemGiven() : void + /** + * @covers ::setFilesystem + */ + public function testRdfPluginShouldContainFilesystemWhenFilesystemGiven(): void { $mockGraph = $this->getMockEasyRdfGraph(); @@ -72,7 +102,10 @@ public function testRdfPluginShouldContainFilesystemWhenFilesystemGiven() : void self::assertPropertyEquals($plugin, 'filesystem', $expected); } - public function testRdfPluginShouldReturnExpectedMethodNameWhenAskedForMethod() : void + /** + * @covers ::getMethod + */ + public function testRdfPluginShouldReturnExpectedMethodNameWhenAskedForMethod(): void { $mockGraph = $this->getMockEasyRdfGraph(); @@ -84,7 +117,7 @@ public function testRdfPluginShouldReturnExpectedMethodNameWhenAskedForMethod() self::assertEquals($expected, $actual); } - public function testRdfPluginShouldComplainWhenHandleCalledWithoutPath() : void + public function testRdfPluginShouldComplainWhenHandleCalledWithoutPath(): void { $mockGraph = $this->getMockEasyRdfGraph(); @@ -97,7 +130,7 @@ public function testRdfPluginShouldComplainWhenHandleCalledWithoutPath() : void $plugin->handle(); } - public function testRdfPluginShouldComplainWhenHandleCalledWithoutFormat() : void + public function testRdfPluginShouldComplainWhenHandleCalledWithoutFormat(): void { $mockGraph = $this->getMockEasyRdfGraph(); @@ -110,7 +143,7 @@ public function testRdfPluginShouldComplainWhenHandleCalledWithoutFormat() : voi $plugin->handle(''); } - public function testRdfPluginShouldComplainWhenHandleCalledWithoutUrl() : void + public function testRdfPluginShouldComplainWhenHandleCalledWithoutUrl(): void { $mockGraph = $this->getMockEasyRdfGraph(); @@ -123,7 +156,10 @@ public function testRdfPluginShouldComplainWhenHandleCalledWithoutUrl() : void $plugin->handle('', ''); } - public function testRdfPluginShouldComplainWhenHandleCalledWithoutFilesystem() : void + /** + * @covers ::handle + */ + public function testRdfPluginShouldComplainWhenHandleCalledWithoutFilesystem(): void { $mockGraph = $this->getMockEasyRdfGraph(); @@ -136,45 +172,122 @@ public function testRdfPluginShouldComplainWhenHandleCalledWithoutFilesystem() : $plugin->handle('', '', ''); } - public function testRdfPluginShouldSerialiseFileContentsWhenHandleCalledWithPathAndFormatAndUrl() : void + /** + * @covers ::handle + * @covers ::setFilesystem + */ + public function testRdfPluginShouldSerialiseFileContentsWhenHandleCalledWithPathAndFormatAndUrl(): void { - $mockContents = 'mock contents'; - $mockFormat = 'mock format'; - $mockPath = 'mock path'; - $mockUrl = 'mock url'; - $mockGraph = $this->getMockEasyRdfGraph(); - $mockFilesystem = $this->getMockFilesystem($mockPath, $mockContents); + $mockFilesystem = $this->getMockFilesystem(self::MOCK_PATH, self::MOCK_CONTENTS); $mockGraph->method('parse') - ->with($mockContents, Format::UNKNOWN, $mockUrl) + ->with(self::MOCK_CONTENTS, Format::UNKNOWN, self::MOCK_URL) ; $mockGraph->method('serialise') - ->with($mockFormat) - ->willReturn($mockContents) + ->with(self::MOCK_FORMAT) + ->willReturn(self::MOCK_CONTENTS) ; $plugin = new ReadRdf($mockGraph); $plugin->setFilesystem($mockFilesystem); /** @noinspection PhpUnhandledExceptionInspection */ - $actual = $plugin->handle($mockPath, $mockFormat, $mockUrl); - $expected = $mockContents; + $actual = $plugin->handle(self::MOCK_PATH, self::MOCK_FORMAT, self::MOCK_URL); + $expected = self::MOCK_CONTENTS; self::assertEquals($expected, $actual); } - // @TODO: Test for when $filesystem->read($path) returns false - // @TODO: Test for when $converter->parse(...) throws EasyRdf_Exception - // @TODO: Test for when $converter->parse(...) output is non-scalar + /** + * @covers ::handle + * + * @uses \Pdsinterop\Rdf\Flysystem\Exception + */ + public function testRdfPluginShouldComplainWhenConverterCanNotParseFileContent(): void + { + $mockGraph = $this->getMockEasyRdfGraph(); + $mockFilesystem = $this->getMockFilesystem(self::MOCK_PATH, self::MOCK_CONTENTS); + + $mockGraph->method('parse') + ->willThrowException(new EasyRdf_Exception()) + ; + + $plugin = new ReadRdf($mockGraph); + $plugin->setFilesystem($mockFilesystem); + + $this->expectException(Exception::class); + + $plugin->handle(self::MOCK_PATH, self::MOCK_FORMAT, self::MOCK_URL); + } + + /** + * @covers ::handle + */ + public function testRdfPluginShouldReturnStringWhenSerialiseResultIsNonScalar(): void + { + $mockGraph = $this->getMockEasyRdfGraph(); + $mockFilesystem = $this->getMockFilesystem(self::MOCK_PATH, self::MOCK_CONTENTS); + + $mockGraph->method('serialise') + ->willReturn(new \stdClass()) + ; + + $plugin = new ReadRdf($mockGraph); + $plugin->setFilesystem($mockFilesystem); + + $actual = $plugin->handle(self::MOCK_PATH, self::MOCK_FORMAT, self::MOCK_URL); + $expected = "(object) array(\n)"; + + self::assertEquals($expected, $actual); + } + + /** + * @covers ::handle + */ + public function testRdfPluginShouldReturnFalseWhenRequestedPathCanNotBeRead(): void + { + $expected = false; + + $mockGraph = $this->getMockEasyRdfGraph(); + $mockFilesystem = $this->getMockFilesystem(self::MOCK_PATH, $expected); + + $plugin = new ReadRdf($mockGraph); + $plugin->setFilesystem($mockFilesystem); + + $actual = $plugin->handle(self::MOCK_PATH, self::MOCK_FORMAT, self::MOCK_URL); + + self::assertEquals($expected, $actual); + } + + /** + * @covers ::handle + */ + public function testRdfPluginShouldComplainWhenRequestedPathDoesNotExist(): void + { + $mockGraph = $this->getMockEasyRdfGraph(); + + $mockFilesystem = $this->getMockBuilder(FilesystemInterface::class) + ->getMock(); + + $mockFilesystem->method('read') + ->willThrowException(new FileNotFoundException(self::MOCK_PATH)); + + $plugin = new ReadRdf($mockGraph); + $plugin->setFilesystem($mockFilesystem); + + $this->expectException(FileNotFoundException::class); + + $plugin->handle(self::MOCK_PATH, self::MOCK_FORMAT, self::MOCK_URL); + } ////////////////////////////// MOCKS AND STUBS \\\\\\\\\\\\\\\\\\\\\\\\\\\\\ /** * @return EasyRdf_Graph | MockObject */ - private function getMockEasyRdfGraph() : EasyRdf_Graph + private function getMockEasyRdfGraph(): EasyRdf_Graph { return $this->getMockBuilder(EasyRdf_Graph::class) ->disableOriginalConstructor() @@ -183,11 +296,11 @@ private function getMockEasyRdfGraph() : EasyRdf_Graph /** * @param string $path - * @param string $fileContents + * @param mixed $fileContents * * @return FilesystemInterface | MockObject */ - private function getMockFilesystem(string $path = '', string $fileContents = '') : FilesystemInterface + private function getMockFilesystem(string $path = '', $fileContents = ''): FilesystemInterface { $mockFilesystem = $this->getMockBuilder(FilesystemInterface::class) ->getMock(); diff --git a/tests/unit/FormatsTest.php b/tests/unit/FormatsTest.php new file mode 100644 index 0000000..b895216 --- /dev/null +++ b/tests/unit/FormatsTest.php @@ -0,0 +1,258 @@ + + * + * @uses \Pdsinterop\Rdf\Enum\Format + */ +class FormatsTest extends TestCase +{ + /////////////////////////////////// TESTS \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ + + /** + * @covers ::getAllExtensions + */ + public function testGetAllExtensions(): void + { + $expected = [ + 'jsonld' => ['jsonld', 'json'], + 'n3' => ['n3'], + 'ntriples' => ['nt'], + 'rdfxml' => ['rdf', 'xrdf', 'html'], + 'turtle' => ['ttl'], + ]; + + $formats = new Formats(); + $actual = $formats->getAllExtensions(); + + $this->assertEquals($expected, $actual); + } + + /** + * @covers ::getAllMimeTypes + */ + public function testGetAllMimeTypes(): void + { + $expected = [ + 'jsonld' => ['application/ld+json'], + 'n3' => ['text/n3', 'text/rdf+n3'], + 'ntriples' => [ + 'application/n-triples', + 'text/plain', + 'text/ntriples', + 'application/ntriples', + 'application/x-ntriples', + ], + 'rdfxml' => ['application/rdf+xml'], + 'turtle' => ['text/turtle', 'application/turtle', 'application/x-turtle'], + ]; + + $formats = new Formats(); + + $actual = $formats->getAllMimeTypes(); + + $this->assertEquals($expected, $actual); + } + + /** + * @covers ::getExtensionsForFormat + * + * @uses \Pdsinterop\Rdf\Formats::getAllExtensions + * + * @dataProvider provideExtensionsForFormat + */ + public function testGetExtensionsForFormat($format, $expected): void + { + $formats = new Formats(); + + $actual = $formats->getExtensionsForFormat($format); + + $this->assertEquals($expected, $actual); + } + + /** + * @covers ::getFormatForExtension + * + * @dataProvider provideFormatForExtension + * + * @uses \Pdsinterop\Rdf\Formats::getAllExtensions + */ + public function testGetFormatForExtension($extension, $expected): void + { + $formats = new Formats(); + + $actual = $formats->getFormatForExtension($extension); + + $this->assertEquals($expected, $actual); + } + + /** + * @covers ::getFormatForMime + * + * @dataProvider provideFormatForMime + * + * @uses \Pdsinterop\Rdf\Formats::getAllMimeTypes + */ + public function testGetFormatForMime($mime, $expected): void + { + $formats = new Formats(); + + $actual = $formats->getFormatForMime($mime); + + $this->assertEquals($expected, $actual); + } + + /** + * @covers ::getMimeForExtension + * + * @uses \Pdsinterop\Rdf\Formats + * + * @dataProvider provideMimeForExtension + */ + public function testGetMimeForExtension($extension, $expected): void + { + $formats = new Formats(); + + $actual = $formats->getMimeForExtension($extension); + + $this->assertEquals($expected, $actual); + } + + /** + * @covers ::getMimeForFormat + * + * @uses \Pdsinterop\Rdf\Formats::getAllMimeTypes + * @uses \Pdsinterop\Rdf\Formats::getMimesForFormat + * + * @dataProvider provideMimeForFormat + */ + public function testGetMimeForFormat($format, $expected): void + { + $formats = new Formats(); + + $actual = $formats->getMimeForFormat($format); + + $this->assertEquals($expected, $actual); + } + + /** + * @covers ::getMimesForFormat + * + * @dataProvider provideMimesForFormat + * + * @uses \Pdsinterop\Rdf\Formats::getAllMimeTypes + */ + public function testGetMimesForFormat($format, $expected): void + { + $formats = new Formats(); + + $actual = $formats->getMimesForFormat($format); + + $this->assertEquals($expected, $actual); + } + + /////////////////////////////// DATAPROVIDERS \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ + + public function provideExtensionsForFormat(): array + { + return [ + 'jsonld' => ['jsonld', ['jsonld', 'json']], + 'n3' => ['n3', ['n3']], + 'ntriples' => ['ntriples', ['nt']], + 'rdfxml' => ['rdfxml', ['rdf', 'xrdf', 'html']], + 'turtle' => ['turtle', ['ttl']], + ]; + } + + public function provideFormatForExtension(): array + { + return [ + 'jsonld (with leading dot)' => ['.jsonld', 'jsonld'], + 'jsonld' => ['jsonld', 'jsonld'], + 'jsonld:json (with leading dot)' => ['.json', 'jsonld'], + 'jsonld:json' => ['json', 'jsonld'], + 'n3 (with leading dot)' => ['.n3', 'n3'], + 'n3' => ['n3', 'n3'], + 'ntriples (with leading dot)' => ['.nt', 'ntriples'], + 'ntriples' => ['nt', 'ntriples'], + 'rdfxml:html (with leading dot)' => ['.html', 'rdfxml'], + 'rdfxml:html' => ['html', 'rdfxml'], + 'rdfxml:rdf (with leading dot)' => ['.rdf', 'rdfxml'], + 'rdfxml:rdf' => ['rdf', 'rdfxml'], + 'rdfxml:xrdf (with leading dot)' => ['.xrdf', 'rdfxml'], + 'rdfxml:xrdf' => ['xrdf', 'rdfxml'], + 'turtle (with leading dot)' => ['.ttl', 'turtle'], + 'turtle' => ['ttl', 'turtle'], + ]; + } + + public function provideFormatForMime(): array + { + return [ + 'jsonld:1' => ['application/ld+json', 'jsonld'], + 'n3:1' => ['text/n3', 'n3'], + 'n3:2' => ['text/rdf+n3', 'n3'], + 'ntriples:1' => ['application/n-triples', 'ntriples'], + 'ntriples:2' => ['text/plain', 'ntriples'], + 'ntriples:3' => ['text/ntriples', 'ntriples'], + 'ntriples:4' => ['application/ntriples', 'ntriples'], + 'ntriples:5' => ['application/x-ntriples', 'ntriples'], + 'rdfxml:1' => ['application/rdf+xml', 'rdfxml'], + 'turtle:1' => ['text/turtle', 'turtle'], + 'turtle:2' => ['application/turtle', 'turtle'], + 'turtle:3' => ['application/x-turtle', 'turtle'], + ]; + } + + public function provideMimeForExtension(): array + { + return [ + 'jsonld (with leading dot)' => ['.jsonld', 'application/ld+json'], + 'jsonld' => ['jsonld', 'application/ld+json'], + 'jsonld:json (with leading dot)' => ['.json', 'application/ld+json'], + 'jsonld:json' => ['json', 'application/ld+json'], + 'n3 (with leading dot)' => ['.n3', 'text/n3'], + 'n3' => ['n3', 'text/n3'], + 'ntriples (with leading dot)' => ['.nt', 'application/n-triples'], + 'ntriples' => ['nt', 'application/n-triples'], + 'rdfxml:html (with leading dot)' => ['.html', 'application/rdf+xml'], + 'rdfxml:html' => ['html', 'application/rdf+xml'], + 'rdfxml:rdf (with leading dot)' => ['.rdf', 'application/rdf+xml'], + 'rdfxml:rdf' => ['rdf', 'application/rdf+xml'], + 'rdfxml:xrdf (with leading dot)' => ['.xrdf', 'application/rdf+xml'], + 'rdfxml:xrdf' => ['xrdf', 'application/rdf+xml'], + 'turtle (with leading dot)' => ['.ttl', 'text/turtle'], + 'turtle' => ['ttl', 'text/turtle'], + ]; + } + + public function provideMimeForFormat(): array + { + return [ + 'jsonld' =>['jsonld', 'application/ld+json'], + 'n3' =>['n3', 'text/n3'], + 'ntriples' =>['ntriples', 'application/n-triples'], + 'rdfxml' => ['rdfxml', 'application/rdf+xml'], + 'turtle' =>['turtle', 'text/turtle'], + ]; + } + + public function provideMimesForFormat(): array + { + return [ + 'jsonld' =>['jsonld', ['application/ld+json']], + 'n3' =>['n3', ['text/n3', 'text/rdf+n3']], + 'ntriples' =>['ntriples', ['application/n-triples', 'text/plain', 'text/ntriples', 'application/ntriples', 'application/x-ntriples']], + 'rdfxml' => ['rdfxml', ['application/rdf+xml']], + 'turtle' =>['turtle', ['text/turtle', 'application/turtle', 'application/x-turtle']], + + ]; + } +}