From b831fc9e98478ae2e0385a4470f5bc0900122808 Mon Sep 17 00:00:00 2001 From: Jared Whiklo Date: Wed, 17 Apr 2024 16:15:07 -0500 Subject: [PATCH] Add Tag Files functions (#65) * Add addTagFile() * Add removeTagFile and tests * Build pushed feature branches * Update action versions * Fix messages * Cover the new private function shared by addTagFile and removeTagFile --- .github/workflows/v5.yml | 11 +- composer.json | 2 +- src/Bag.php | 96 +++++- src/BagUtils.php | 41 +++ tests/BagTest.php | 12 +- tests/BagUtilsTest.php | 93 ++++++ tests/ExtendedBagTest.php | 311 ++++++++++++++++++- tests/ManifestTest.php | 2 +- tests/resources/tagFiles/SomeSpecialTags.txt | 2 + 9 files changed, 533 insertions(+), 37 deletions(-) create mode 100644 tests/resources/tagFiles/SomeSpecialTags.txt diff --git a/.github/workflows/v5.yml b/.github/workflows/v5.yml index 2bf0cec..9a05a8b 100644 --- a/.github/workflows/v5.yml +++ b/.github/workflows/v5.yml @@ -4,6 +4,9 @@ on: pull_request: branches: - "v5" + push: + branches: + - "feature/v5/*" jobs: build: @@ -19,7 +22,7 @@ jobs: name: PHP ${{ matrix.php-versions }} - OS ${{ matrix.host-os }} steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup PHP uses: shivammathur/setup-php@v2 @@ -35,7 +38,7 @@ jobs: if: ${{ startsWith( matrix.host-os , 'ubuntu') }} - name: Cache dependencies (Ubuntu) - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ${{ steps.composercache-ubuntu.outputs.dir }} key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} @@ -50,7 +53,7 @@ jobs: if: ${{ startsWith( matrix.host-os , 'windows') }} - name: Cache dependencies (Windows) - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ${{ steps.composercache-windows.outputs.dir }} key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} @@ -68,7 +71,7 @@ jobs: run: composer phpunit - name: Upload coverage to Codecov - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v4 with: file: ./clover.xml fail_ci_if_error: true diff --git a/composer.json b/composer.json index 64ea5d6..dee7fff 100644 --- a/composer.json +++ b/composer.json @@ -50,7 +50,7 @@ "./vendor/bin/phpcpd --suffix='.php' src" ], "phpunit": [ - "phpdbg -qrr ./vendor/bin/phpunit -d memory_limit=-1 --testsuite BagIt" + "phpdbg -qrr ./vendor/bin/phpunit -d memory_limit=-1 --verbose --testsuite BagIt" ], "test": [ "@check", diff --git a/src/Bag.php b/src/Bag.php index 4f29181..f4f9f19 100644 --- a/src/Bag.php +++ b/src/Bag.php @@ -670,9 +670,7 @@ public function removeBagInfoTagValue(string $tag, string $value, bool $case_sen */ public function addBagInfoTag(string $tag, string $value): void { - if (!$this->isExtended) { - throw new BagItException("This bag is not extended, you need '\$bag->setExtended(true);'"); - } + $this->setExtended(true); $internal_tag = self::trimLower($tag); if (in_array($internal_tag, self::BAG_INFO_GENERATED_ELEMENTS)) { throw new BagItException("Field $tag is auto-generated and cannot be manually set."); @@ -690,11 +688,9 @@ public function addBagInfoTag(string $tag, string $value): void */ public function addBagInfoTags(array $tags): void { - if (!$this->isExtended) { - throw new BagItException("This bag is not extended, you need '\$bag->setExtended(true);'"); - } + $this->setExtended(true); $normalized_keys = array_keys($tags); - $normalized_keys = array_map('self::trimLower', $normalized_keys); + $normalized_keys = array_map(self::class . '::trimLower', $normalized_keys); $overlap = array_intersect($normalized_keys, self::BAG_INFO_GENERATED_ELEMENTS); if (count($overlap) !== 0) { throw new BagItException( @@ -892,10 +888,10 @@ public function setAlgorithm(string $algorithm): void */ public function setAlgorithms(array $algorithms): void { - $internal_names = array_map('self::getHashName', $algorithms); + $internal_names = array_map(self::class . '::getHashName', $algorithms); $valid_algorithms = array_filter($internal_names, [$this, 'hashIsSupported']); if (count($valid_algorithms) !== count($algorithms)) { - throw new BagItException("One or more of the algorithms provided are supported."); + throw new BagItException("One or more of the algorithms provided are NOT supported."); } $this->setAlgorithmsInternal($valid_algorithms); } @@ -1152,16 +1148,14 @@ public function pathInBagData(string $filepath): bool * * @param string $path * The file just deleted. + * @throws FilesystemException If we can't delete the directory. + * @throws BagItException If the directory is outside the data directory. */ public function checkForEmptyDir(string $path): void { $parentPath = dirname($path); if (str_starts_with($this->makeRelative($parentPath), "data/")) { - $files = scandir($parentPath); - $payload = array_diff($files, [".", ".."]); - if (count($payload) == 0) { - rmdir($parentPath); - } + BagUtils::deleteEmptyDirTree($parentPath, $this->getDataDirectory()); } } @@ -1193,10 +1187,84 @@ public function upgrade(): void $this->update(); } + /** + * Add a special tag file to the bag. + * @param string $source Full path to the tag file. + * @param string $dest Relative path for the destination. + * + * @throws BagItException Various errors related to the source and destination locations and access. + * @throws FilesystemException Issues reading from or writing to the filesystem. + */ + public function addTagFile(string $source, string $dest): void + { + if (!file_exists($source) || !is_file($source) || !is_readable($source)) { + throw new BagItException("$source does not exist, is not a file or is not readable."); + } + $this->checkTagFileConstraints($dest); + $external = $this->makeAbsolute($dest); + if (file_exists($external)) { + throw new BagItException("Tag file ($dest) already exists in the bag."); + } + $this->setExtended(true); + $parentDirs = dirname($external); + if ($parentDirs !== $this->getBagRoot() && !file_exists($parentDirs)) { + // Create any missing tag file directories. + BagUtils::checkedMkdir($parentDirs, 0777, true); + } + BagUtils::checkedCopy($source, $external); + $this->changed = true; + } + + /** + * Remove a tag file and any empty directories it leaves behind. + * @param string $dest The relative path to the tag file. + * @return void + * @throws BagItException If the file does not exist, is not inside the bag root or is a reserved file. + * @throws FilesystemException If there are issues deleting the file or directories. + */ + public function removeTagFile(string $dest): void + { + $this->checkTagFileConstraints($dest); + $external = $this->makeAbsolute($dest); + if (!file_exists($external)) { + throw new BagItException("Tag file ($dest) does not exist in the bag."); + } + BagUtils::checkedUnlink($external); + BagUtils::deleteEmptyDirTree(dirname($external), $this->getBagRoot()); + $this->changed = true; + } + /* * XXX: Private functions */ + /** + * Common checks for interactions with custom tag files. + * @param string $tagFilePath The relative path to the tag file. + * @return void + * @throws BagItException If the tag file is not in the bag root, is in the data directory, or is a reserved file. + */ + private function checkTagFileConstraints(string $tagFilePath): void + { + $external = $this->makeAbsolute($tagFilePath); + $relativePath = $this->makeRelative($external); + if ($relativePath === "") { + throw new BagItException("Tag files must be inside the bag root."); + } + if (str_starts_with(strtolower($relativePath), "data/")) { + throw new BagItException("Tag files must be in the bag root or a tag file directory, " . + "use ->addFile() to add data files."); + } + if (in_array(strtolower($relativePath), ['bagit.txt', 'bag-info.txt', 'fetch.txt'])) { + throw new BagItException("You cannot alter reserved file ($tagFilePath) file with your own tag file."); + } elseif ( + str_starts_with(strtolower($relativePath), 'tagmanifest-') || + str_starts_with(strtolower($relativePath), 'manifest-') + ) { + throw new BagItException("You cannot alter a manifest or tag manifest file with your own tag file."); + } + } + /** * Load a bag from disk. * diff --git a/src/BagUtils.php b/src/BagUtils.php index b39a792..6bfa77c 100644 --- a/src/BagUtils.php +++ b/src/BagUtils.php @@ -5,6 +5,7 @@ namespace whikloj\BagItTools; use TypeError; +use whikloj\BagItTools\Exceptions\BagItException; use whikloj\BagItTools\Exceptions\FilesystemException; /** @@ -324,6 +325,18 @@ public static function checkedFwrite($fp, string $content): void } } + /** + * Remove a directory and check if it succeeded. + * @param string $path The path to remove. + * @throws FilesystemException If the call to rmdir() fails. + */ + public static function checkedRmDir(string $path): void + { + if (!@rmdir($path)) { + throw new FilesystemException("Unable to remove directory $path"); + } + } + /** * Decode a file path according to the special rules of the spec. * @@ -410,4 +423,32 @@ public static function standardizePathSeparators(string $path): string { return str_replace('\\', '/', $path); } + + /** + * Walk up a path as far as the rootDir and delete empty directories. + * @param string $path The path to check. + * @param string $rootDir The root to not remove . + * + * @throws BagItException If the path is not within the bag root. + * @throws FilesystemException If we can't remove a directory + */ + public static function deleteEmptyDirTree(string $path, string $rootDir): void + { + if (rtrim(strtolower($path), '/') === rtrim(strtolower($rootDir), '/')) { + return; + } + if (!str_starts_with($path, $rootDir)) { + throw new BagItException("Path is not within the root directory."); + } + if (file_exists($path) && is_dir($path)) { + $parent = dirname($path); + $files = array_diff(scandir($path), [".", ".."]); + if (count($files) === 0) { + self::checkedRmDir($path); + } + if ($parent !== $rootDir) { + self::deleteEmptyDirTree($parent, $rootDir); + } + } + } } diff --git a/tests/BagTest.php b/tests/BagTest.php index 6d375fb..b1c135a 100644 --- a/tests/BagTest.php +++ b/tests/BagTest.php @@ -583,7 +583,7 @@ public function testSetAlgorithmsFailure(): void $bag = Bag::create($this->tmpdir); $this->assertArrayEquals(['sha512'], $bag->getAlgorithms()); $this->expectException(BagItException::class); - $this->expectExceptionMessage("One or more of the algorithms provided are supported."); + $this->expectExceptionMessage("One or more of the algorithms provided are NOT supported."); $bag->setAlgorithms(['sha1', 'SHA-224', "bad-algorithm"]); } @@ -1013,17 +1013,17 @@ public function testFailOnEncodedBagIt(): void } /** - * Test that for a non-extended bag, trying to add bag-info tags throws an error. + * Test that for a non-extended bag, trying to add bag-info tags no longer throws an error. * @group Bag * @covers ::addBagInfoTag */ public function testAddBagInfoWhenNotExtended(): void { - $this->expectException(BagItException::class); - $this->expectExceptionMessage("This bag is not extended, you need '\$bag->setExtended(true);'"); - $bag = Bag::create($this->tmpdir); - $bag->addBagInfoTag("Contact-Name", "Jared Whiklo"); + $this->assertFalse($bag->isExtended()); + $bag->addBagInfoTag("Contact-Name", "Bob Smith"); + $this->assertTrue($bag->isExtended()); + $this->assertArrayEquals(["Bob Smith"], $bag->getBagInfoByTag("Contact-Name")); } /** diff --git a/tests/BagUtilsTest.php b/tests/BagUtilsTest.php index 2fb3356..4d17621 100644 --- a/tests/BagUtilsTest.php +++ b/tests/BagUtilsTest.php @@ -6,6 +6,7 @@ use Exception; use whikloj\BagItTools\BagUtils; +use whikloj\BagItTools\Exceptions\BagItException; use whikloj\BagItTools\Exceptions\FilesystemException; /** @@ -209,6 +210,29 @@ public function testCheckedFwrite(): void BagUtils::checkedFwrite($fp, "Some example text"); } + /** + * @covers ::checkedRmDir + */ + public function testCheckedRmDirFailure(): void + { + $this->expectException(FilesystemException::class); + $this->expectExceptionMessage("Unable to remove directory $this->tmpdir"); + + // try to delete a non-existent file. + BagUtils::checkedRmDir($this->tmpdir); + } + + /** + * @covers ::checkedRmDir + */ + public function testCheckedRmDirSuccess(): void + { + mkdir($this->tmpdir); + $this->assertDirectoryExists($this->tmpdir); + BagUtils::checkedRmDir($this->tmpdir); + $this->assertDirectoryDoesNotExist($this->tmpdir); + } + /** * @covers ::checkUnencodedFilepath */ @@ -239,4 +263,73 @@ public function testStandardizeFilePaths(): void $this->assertEquals($item[0], BagUtils::standardizePathSeparators($item[1])); } } + + /** + * @covers ::deleteEmptyDirTree + */ + public function testDeleteEmptyTreeOutsideRoot(): void + { + mkdir($this->tmpdir); + $parent = dirname($this->tmpdir); + $this->assertDirectoryExists($this->tmpdir); + + $this->expectException(BagItException::class); + $this->expectExceptionMessage("Path is not within the root directory."); + + BagUtils::deleteEmptyDirTree($parent, $this->tmpdir); + } + + /** + * @covers ::deleteEmptyDirTree + */ + public function testDeleteMultipleLevels(): void + { + mkdir($this->tmpdir); + $bottomRung = $this->tmpdir . "/level1/level2/level3"; + mkdir($bottomRung, 0777, true); + $this->assertDirectoryExists($bottomRung); + $this->assertDirectoryExists($this->tmpdir . "/level1/level2"); + $this->assertDirectoryExists($this->tmpdir . "/level1"); + BagUtils::deleteEmptyDirTree($bottomRung, $this->tmpdir); + $this->assertDirectoryDoesNotExist($bottomRung); + $this->assertDirectoryDoesNotExist($this->tmpdir . "/level1/level2"); + $this->assertDirectoryDoesNotExist($this->tmpdir . "/level1"); + } + + /** + * @covers ::deleteEmptyDirTree + */ + public function testDeleteMultipleLevelsStopInMiddle(): void + { + mkdir($this->tmpdir); + $bottomRung = $this->tmpdir . "/level1/level2/level3"; + $someFile = $this->tmpdir . "/level1/level2/someFile.txt"; + mkdir($bottomRung, 0777, true); + // Create a file in the middle level. + touch($someFile); + + $this->assertDirectoryExists($bottomRung); + $this->assertDirectoryExists($this->tmpdir . "/level1/level2"); + $this->assertDirectoryExists($this->tmpdir . "/level1"); + BagUtils::deleteEmptyDirTree($bottomRung, $this->tmpdir); + + $this->assertDirectoryDoesNotExist($bottomRung); + // Middle level should still exist. + $this->assertDirectoryExists($this->tmpdir . "/level1/level2"); + $this->assertDirectoryExists($this->tmpdir . "/level1"); + // Remove the file. + unlink($someFile); + + BagUtils::deleteEmptyDirTree($bottomRung, $this->tmpdir); + // Because we specified a non-existent directory, we didn't traverse anything. + $this->assertDirectoryDoesNotExist($bottomRung); + $this->assertDirectoryExists($this->tmpdir . "/level1/level2"); + $this->assertDirectoryExists($this->tmpdir . "/level1"); + + BagUtils::deleteEmptyDirTree($this->tmpdir . "/level1/level2", $this->tmpdir); + // Now all the directories should be removed. + $this->assertDirectoryDoesNotExist($bottomRung); + $this->assertDirectoryDoesNotExist($this->tmpdir . "/level1/level2"); + $this->assertDirectoryDoesNotExist($this->tmpdir . "/level1"); + } } diff --git a/tests/ExtendedBagTest.php b/tests/ExtendedBagTest.php index ecbd039..c4abc9b 100644 --- a/tests/ExtendedBagTest.php +++ b/tests/ExtendedBagTest.php @@ -418,7 +418,6 @@ public function testSetAlgorithms(): void public function testSetBagInfoElement(): void { $bag = Bag::create($this->tmpdir); - $bag->setExtended(true); $bag->addBagInfoTag('Contact-NAME', 'Monty Hall'); $this->assertCount(1, $bag->getBagInfoData()); $this->assertTrue($bag->hasBagInfoTag('contact-name')); @@ -452,7 +451,6 @@ public function testSetBagInfoElement(): void public function testSetGeneratedField(): void { $bag = Bag::create($this->tmpdir); - $bag->setExtended(true); $bag->addBagInfoTag('Source-organization', 'Planet Earth'); // Doesn't match due to underscore instead of hyphen. $bag->addBagInfoTag('PAYLOAD_OXUM', '123456.12'); @@ -474,7 +472,6 @@ public function testSetGeneratedField(): void public function testAddBagInfoTags(): void { $bag = Bag::create($this->tmpdir); - $bag->setExtended(true); $this->assertArrayEquals([], $bag->getBagInfoData()); $baginfo = $bag->getBagRoot() . '/bag-info.txt'; @@ -504,7 +501,6 @@ public function testAddBagInfoTags(): void public function testAddBagInfoTagsMultiple(): void { $bag = Bag::create($this->tmpdir); - $bag->setExtended(true); $this->assertArrayEquals([], $bag->getBagInfoData()); $baginfo = $bag->getBagRoot() . '/bag-info.txt'; @@ -536,7 +532,6 @@ public function testAddBagInfoTagsMultiple(): void public function testAddBagInfoTagsGenerated(): void { $bag = Bag::create($this->tmpdir); - $bag->setExtended(true); $this->assertArrayEquals([], $bag->getBagInfoData()); $inputTags = [ @@ -551,13 +546,14 @@ public function testAddBagInfoTagsGenerated(): void } /** - * Test that addBagInfoTags() throws an exception if the bag is not extended. + * Test that addBagInfoTags() no longer throws an exception if the bag is not extended. * @group Extended * @covers ::addBagInfoTags */ public function testAddBagInfoTagsNotExtended(): void { $bag = Bag::create($this->tmpdir); + $this->assertFalse($bag->isExtended()); $inputTags = [ 'Source-organization' => 'The Pyramid', 'CONTACT-NAME' => [ @@ -565,9 +561,12 @@ public function testAddBagInfoTagsNotExtended(): void 'Monty Hall', ], ]; - $this->expectException(BagItException::class); - $this->expectExceptionMessage("This bag is not extended, you need '\$bag->setExtended(true);'"); $bag->addBagInfoTags($inputTags); + $this->assertTrue($bag->isExtended()); + $this->assertTrue($bag->hasBagInfoTag('source-organization')); + $this->assertTrue($bag->hasBagInfoTag('contact-name')); + $this->assertArrayEquals(['The Pyramid'], $bag->getBagInfoByTag('source-organization')); + $this->assertArrayEquals(['Monty Hall', 'Bob Barker'], $bag->getBagInfoByTag('contact-name')); } /** @@ -622,7 +621,6 @@ public function testGetManifests(): void $this->tmpdir = $this->prepareBasicTestBag(); $bag = Bag::load($this->tmpdir); $payloads = $bag->getPayloadManifests(); - $this->assertTrue(is_array($payloads)); $this->assertCount(1, $payloads); $tags = $bag->getTagManifests(); $this->assertCount(0, $tags); @@ -640,7 +638,6 @@ public function testGetManifestsExtended(): void $this->tmpdir = $this->prepareExtendedTestBag(); $bag = Bag::load($this->tmpdir); $payloads = $bag->getPayloadManifests(); - $this->assertTrue(is_array($payloads)); $this->assertCount(1, $payloads); $tags = $bag->getTagManifests(); $this->assertTrue(is_array($tags)); @@ -711,7 +708,6 @@ public function testCalculatedBagSize(): void public function testLongBagInfoLinesWrap(): void { $bag = Bag::create($this->tmpdir); - $bag->setExtended(true); $title = 'A really long long long long long long long long long long long title' . ' with a colon : between and more information are on the way'; $bag->addBagInfoTag('Title', $title); @@ -992,4 +988,297 @@ public function testExtendedBagWithoutBagInfo(): void $bag = Bag::load($this->tmpdir); $this->assertTrue($bag->isValid()); } + + /** + * Test trying to add a tag file with a non-existant file. + * @group Extended + * @covers ::addTagFile + * @covers ::checkTagFileConstraints + */ + public function testAddTagFileFileDoesNotExist(): void + { + $bag = Bag::create($this->tmpdir); + $this->expectException(BagItException::class); + $this->expectExceptionMessage("/path/to/nowhere.txt does not exist, is not a file or is not readable."); + $bag->addTagFile('/path/to/nowhere.txt', 'nowhere.txt'); + } + + /** + * Test trying to add a tag file over the bag-info.txt file. + * @group Extended + * @covers ::addTagFile + * @covers ::checkTagFileConstraints + */ + public function testAddTagFileCannotOverwriteBagInfo(): void + { + $special_file = self::TEST_RESOURCES . '/tagFiles/SomeSpecialTags.txt'; + $bag = Bag::create($this->tmpdir); + $bag->addBagInfoTag('Contact-NAME', 'Monty Hall'); + $this->expectException(BagItException::class); + $this->expectExceptionMessage("You cannot alter reserved file (bag-info.txt) file with your own tag file."); + $bag->addTagFile($special_file, 'bag-info.txt'); + } + + /** + * Test trying to add a tag file over the bagit.txt file. + * @group Extended + * @covers ::addTagFile + * @covers ::checkTagFileConstraints + */ + public function testAddTagCannotOverwriteBagIt(): void + { + $special_file = self::TEST_RESOURCES . '/tagFiles/SomeSpecialTags.txt'; + $bag = Bag::create($this->tmpdir); + $bag->addBagInfoTag('Contact-NAME', 'Monty Hall'); + $this->expectException(BagItException::class); + $this->expectExceptionMessage("You cannot alter reserved file (bagit.txt) file with your own tag file."); + $bag->addTagFile($special_file, 'bagit.txt'); + } + + /** + * Test trying to add a tag file over a payload manifest file. + * @group Extended + * @covers ::addTagFile + * @covers ::checkTagFileConstraints + */ + public function testAddTagCannotOverwritePayloadManifest(): void + { + $special_file = self::TEST_RESOURCES . '/tagFiles/SomeSpecialTags.txt'; + $bag = Bag::create($this->tmpdir); + $bag->addBagInfoTag('Contact-NAME', 'Monty Hall'); + $bag->addAlgorithm('sha256'); + $this->expectException(BagItException::class); + $this->expectExceptionMessage("You cannot alter a manifest or tag manifest file with your own tag file."); + $bag->addTagFile($special_file, 'manifest-sha256.txt'); + } + + /** + * Test trying to add a tag file over a tag manifest file. + * @group Extended + * @covers ::addTagFile + * @covers ::checkTagFileConstraints + */ + public function testAddTagCannotOverwriteTagManifest(): void + { + $special_file = self::TEST_RESOURCES . '/tagFiles/SomeSpecialTags.txt'; + $bag = Bag::create($this->tmpdir); + $bag->setExtended(true); + $bag->addBagInfoTag('Contact-NAME', 'Monty Hall'); + $bag->addAlgorithm('sha256'); + $this->expectException(BagItException::class); + $this->expectExceptionMessage("You cannot alter a manifest or tag manifest file with your own tag file."); + $bag->addTagFile($special_file, 'tagmanifest-sha256.txt'); + } + + /** + * Test trying to add a tag file to the data directory. + * @group Extended + * @covers ::addTagFile + * @covers ::checkTagFileConstraints + */ + public function testAddTagCannotBeInDataDirectory(): void + { + $special_file = self::TEST_RESOURCES . '/tagFiles/SomeSpecialTags.txt'; + $bag = Bag::create($this->tmpdir); + $this->expectException(BagItException::class); + $this->expectExceptionMessage("Tag files must be in the bag root or a tag file directory"); + $bag->addTagFile($special_file, 'data/special.txt'); + } + + /** + * Test trying to add a tag file to the data directory. + * @group Extended + * @covers ::addTagFile + * @covers ::checkTagFileConstraints + */ + public function testAddTagCannotBeInDataDirectory2(): void + { + $special_file = self::TEST_RESOURCES . '/tagFiles/SomeSpecialTags.txt'; + $bag = Bag::create($this->tmpdir); + $this->expectException(BagItException::class); + $this->expectExceptionMessage("Tag files must be in the bag root or a tag file directory"); + $bag->addTagFile($special_file, './data/special.txt'); + } + + /** + * Test adding a tag file to the bag. + * @group Extended + * @covers ::addTagFile + */ + public function testAddTagNotOverwrite(): void + { + $special_file = self::TEST_RESOURCES . '/tagFiles/SomeSpecialTags.txt'; + $bag = Bag::create($this->tmpdir); + $bag->addTagFile($special_file, 'special.txt'); + $this->assertFileExists($bag->getBagRoot() . '/special.txt'); + $this->expectException(BagItException::class); + $this->expectExceptionMessage("Tag file (special.txt) already exists in the bag."); + $bag->addTagFile($special_file, 'special.txt'); + } + + /** + * Test adding a tag file to the bag. + * @group Extended + * @covers ::addTagFile + * @covers ::checkTagFileConstraints + */ + public function testAddTagFileSuccess(): void + { + $special_file = self::TEST_RESOURCES . '/tagFiles/SomeSpecialTags.txt'; + $bag = Bag::create($this->tmpdir); + $bag->addTagFile($special_file, 'special.txt'); + $this->assertFileExists($bag->getBagRoot() . '/special.txt'); + } + + /** + * Test adding tag files in a subdirectory + * @group Extended + * @covers ::addTagFile + */ + public function testAddTagFileInSubDir(): void + { + $special_file = self::TEST_RESOURCES . '/tagFiles/SomeSpecialTags.txt'; + $bag = Bag::create($this->tmpdir); + $bag->addTagFile($special_file, 'special/special.txt'); + $this->assertFileExists($bag->getBagRoot() . '/special/special.txt'); + $this->assertFileExists($bag->getBagRoot() . '/special'); + $bag->addTagFile($special_file, 'special/special2.txt'); + $this->assertFileExists($bag->getBagRoot() . '/special/special2.txt'); + } + + /** + * Test trying to add a tag file with a non-existant file. + * @group Extended + * @covers ::removeTagFile + */ + public function testRemoveTagFileFileDoesNotExist(): void + { + $bag = Bag::create($this->tmpdir); + $this->expectException(BagItException::class); + $this->expectExceptionMessage("Tag file (nowhere.txt) does not exist in the bag."); + $bag->removeTagFile('nowhere.txt'); + } + + /** + * Test trying to remove the bag-info.txt file using removeTagFile. + * @group Extended + * @covers ::removeTagFile + */ + public function testRemoveTagFileCannotOverwriteBagInfo(): void + { + $bag = Bag::create($this->tmpdir); + $bag->addBagInfoTag('Contact-NAME', 'Monty Hall'); + $bag->update(); // Update to create the bag-info.txt on disk. + $this->assertTrue(file_exists($bag->getBagRoot() . '/bag-info.txt')); + $this->expectException(BagItException::class); + $this->expectExceptionMessage("You cannot alter reserved file (bag-info.txt) file with your own tag file."); + $bag->removeTagFile('bag-info.txt'); + $this->assertTrue(file_exists($bag->getBagRoot() . '/bag-info.txt')); + } + + /** + * Test trying to remove the bagit.txt file using removeTagFile. + * @group Extended + * @covers ::addTagFile + */ + public function testRemoveTagCannotOverwriteBagIt(): void + { + $bag = Bag::create($this->tmpdir); + $this->assertTrue(file_exists($bag->getBagRoot() . '/bagit.txt')); + $this->expectException(BagItException::class); + $this->expectExceptionMessage("You cannot alter reserved file (bagit.txt) file with your own tag file."); + $bag->removeTagFile('bagit.txt'); + $this->assertTrue(file_exists($bag->getBagRoot() . '/bagit.txt')); + } + + /** + * Test trying to remove a payload manifest file using removeTagFile. + * @group Extended + * @covers ::removeTagFile + */ + public function testRemoveTagCannotOverwritePayloadManifest(): void + { + $bag = Bag::create($this->tmpdir); + $bag->addBagInfoTag('Contact-NAME', 'Monty Hall'); + $bag->setAlgorithm('sha256'); + $bag->update(); + $this->assertTrue(file_exists($bag->getBagRoot() . '/manifest-sha256.txt')); + $this->expectException(BagItException::class); + $this->expectExceptionMessage("You cannot alter a manifest or tag manifest file with your own tag file."); + $bag->removeTagFile('manifest-sha256.txt'); + $this->assertTrue(file_exists($bag->getBagRoot() . '/manifest-sha256.txt')); + } + + /** + * Test trying to remove a tag manifest file using removeTagFile. + * @group Extended + * @covers ::removeTagFile + */ + public function testRemoveTagCannotOverwriteTagManifest(): void + { + $bag = Bag::create($this->tmpdir); + $bag->addBagInfoTag('Contact-NAME', 'Monty Hall'); + $bag->setAlgorithm('sha256'); + $bag->update(); + $this->assertTrue(file_exists($bag->getBagRoot() . '/tagmanifest-sha256.txt')); + $this->expectException(BagItException::class); + $this->expectExceptionMessage("You cannot alter a manifest or tag manifest file with your own tag file."); + $bag->removeTagFile('tagmanifest-sha256.txt'); + $this->assertTrue(file_exists($bag->getBagRoot() . '/tagmanifest-sha256.txt')); + } + + /** + * Test trying to remove a file from the data directory using removeTagFile. + * @group Extended + * @covers ::removeTagFile + */ + public function testRemoveTagCannotBeInDataDirectory(): void + { + $bag = Bag::create($this->tmpdir); + $bag->addFile(self::TEST_RESOURCES . '/text/empty.txt', 'data/empty.txt'); + $this->assertTrue(file_exists($bag->getDataDirectory() . '/empty.txt')); + $this->expectException(BagItException::class); + $this->expectExceptionMessage("Tag files must be in the bag root or a tag file directory"); + $bag->removeTagFile('data/special.txt'); + $this->assertTrue(file_exists($bag->getDataDirectory() . '/empty.txt')); + } + + /** + * Test trying to remove a file from the data directory using removeTagFile. + * @group Extended + * @covers ::removeTagFile + */ + public function testRemoveTagCannotBeInDataDirectory2(): void + { + $bag = Bag::create($this->tmpdir); + $bag->addFile(self::TEST_RESOURCES . '/text/empty.txt', 'data/empty.txt'); + $this->assertTrue(file_exists($bag->getDataDirectory() . '/empty.txt')); + $this->expectException(BagItException::class); + $this->expectExceptionMessage("Tag files must be in the bag root or a tag file directory"); + $bag->removeTagFile('./data/special.txt'); + $this->assertTrue(file_exists($bag->getDataDirectory() . '/empty.txt')); + } + + /** + * Test adding and removing multiple tag files in a tag file subdirectory. + * @group Extended + * @covers ::addTagFile + * @covers ::removeTagFile + */ + public function testAddAndRemoveTagFileInSubDir(): void + { + $special_file = self::TEST_RESOURCES . '/tagFiles/SomeSpecialTags.txt'; + $bag = Bag::create($this->tmpdir); + $bag->addTagFile($special_file, 'special/special.txt'); + $bag->addTagFile($special_file, 'special/special2.txt'); + $this->assertFileExists($bag->getBagRoot() . '/special/special.txt'); + $this->assertFileExists($bag->getBagRoot() . '/special/special2.txt'); + $bag->removeTagFile('special/special2.txt'); + $this->assertFileExists($bag->getBagRoot() . '/special/special.txt'); + $this->assertFileDoesNotExist($bag->getBagRoot() . '/special/special2.txt'); + $bag->removeTagFile('special/special.txt'); + $this->assertFileDoesNotExist($bag->getBagRoot() . '/special/special.txt'); + $this->assertFileDoesNotExist($bag->getBagRoot() . '/special/special2.txt'); + $this->assertFileDoesNotExist($bag->getBagRoot() . '/special'); + } } diff --git a/tests/ManifestTest.php b/tests/ManifestTest.php index 8a70408..1662259 100644 --- a/tests/ManifestTest.php +++ b/tests/ManifestTest.php @@ -51,7 +51,7 @@ public function testCheckManifests(): void 'tag' => $bag->getBagRoot() . '/tagmanifest-sha256.txt', ]; $bag->setExtended(true); - $bag->addBagInfoTag('Contact-name', 'Jared Whiklo'); + $bag->addBagInfoTag('Contact-name', 'Bob Smith'); $bag->setAlgorithm('sha256'); $bag->createFile("This is some sample text", 'some/directory/file.txt'); diff --git a/tests/resources/tagFiles/SomeSpecialTags.txt b/tests/resources/tagFiles/SomeSpecialTags.txt new file mode 100644 index 0000000..ad835ee --- /dev/null +++ b/tests/resources/tagFiles/SomeSpecialTags.txt @@ -0,0 +1,2 @@ +Special-File-ID: 010101 +Special-File-User: Bob Smith \ No newline at end of file