Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
Latest
------

### Changes
* [#13](https://github.com/cleverage/archive-process-bundle/issues/13) Add missing tests: ZipTask and UnzipTask (options precedence and validation, missing or unreadable files, overwriting, open failures), bundle and DI extension.

### Fixes
* [#15](https://github.com/cleverage/archive-process-bundle/issues/15) Fix ZipTask and UnzipTask: resolve the options on every execution, so that each input is used (the options of the first input were reused for the following ones). Update documentation, add tests.
* [#16](https://github.com/cleverage/archive-process-bundle/issues/16) Fix ZipTask: only remove the leading `files_base_path` from the file paths (every occurrence was removed), and read the files at the given path when `files_base_path` is empty (relative paths were resolved from the filesystem root). Update documentation, add tests.
Expand Down
30 changes: 30 additions & 0 deletions tests/CleverAgeArchiveProcessBundleTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

declare(strict_types=1);

/*
* This file is part of the CleverAge/ArchiveProcessBundle package.
*
* Copyright (c) Clever-Age
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace CleverAge\ArchiveProcessBundle\Tests;

use CleverAge\ArchiveProcessBundle\CleverAgeArchiveProcessBundle;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\TestCase;

#[CoversClass(CleverAgeArchiveProcessBundle::class)]
class CleverAgeArchiveProcessBundleTest extends TestCase
{
public function testPathIsTheBundleRoot(): void
{
$path = (new CleverAgeArchiveProcessBundle())->getPath();

self::assertSame(\dirname(__DIR__), $path);
self::assertDirectoryExists($path.'/config/services');
}
}
55 changes: 55 additions & 0 deletions tests/DependencyInjection/CleverAgeArchiveProcessExtensionTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<?php

declare(strict_types=1);

/*
* This file is part of the CleverAge/ArchiveProcessBundle package.
*
* Copyright (c) Clever-Age
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace CleverAge\ArchiveProcessBundle\Tests\DependencyInjection;

use CleverAge\ArchiveProcessBundle\DependencyInjection\CleverAgeArchiveProcessExtension;
use CleverAge\ArchiveProcessBundle\Task\UnzipTask;
use CleverAge\ArchiveProcessBundle\Task\ZipTask;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;
use Symfony\Component\DependencyInjection\ContainerBuilder;

#[CoversClass(CleverAgeArchiveProcessExtension::class)]
class CleverAgeArchiveProcessExtensionTest extends TestCase
{
/**
* @return iterable<string, array{string, class-string}>
*/
public static function provideTasks(): iterable
{
yield 'zip' => ['cleverage_archive_process.task.zip', ZipTask::class];
yield 'unzip' => ['cleverage_archive_process.task.unzip', UnzipTask::class];
}

/**
* @param class-string $class
*/
#[DataProvider('provideTasks')]
public function testTaskIsRegistered(string $id, string $class): void
{
$container = new ContainerBuilder();
(new CleverAgeArchiveProcessExtension())->load([], $container);

$definition = $container->getDefinition($id);
self::assertSame($class, $definition->getClass());
// Tasks are stateful: each process execution must get its own instance
self::assertFalse($definition->isShared());

// Referenced as '@<class>' in process configurations
$alias = $container->getAlias($class);
self::assertSame($id, (string) $alias);
self::assertTrue($alias->isPublic());
}
}
94 changes: 94 additions & 0 deletions tests/Task/UnzipTaskTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException;
use Symfony\Component\OptionsResolver\Exception\MissingOptionsException;
use Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException;

#[CoversClass(UnzipTask::class)]
class UnzipTaskTest extends TestCase
Expand Down Expand Up @@ -111,6 +114,97 @@ public function testNonArrayInput(): void
}
}

public function testInputOverridesConfiguredOptions(): void
{
[$task, $state] = $this->createTask([
'filename' => $this->dir.'/archive1.zip',
'destination' => $this->dir.'/configured',
]);

self::assertSame($this->dir.'/input', $this->execute($task, $state, ['destination' => $this->dir.'/input']));
self::assertFileExists($this->dir.'/input/file1.txt');
self::assertDirectoryDoesNotExist($this->dir.'/configured');
}

public function testExistingFilesAreOverwrittenOtherFilesAreKept(): void
{
mkdir($this->dir.'/destination');
file_put_contents($this->dir.'/destination/file1.txt', 'old content');
file_put_contents($this->dir.'/destination/other.txt', 'other content');
[$task, $state] = $this->createTask([
'filename' => $this->dir.'/archive1.zip',
'destination' => $this->dir.'/destination',
]);

$this->execute($task, $state, null);

self::assertStringEqualsFile($this->dir.'/destination/file1.txt', 'content of file1.txt');
self::assertStringEqualsFile($this->dir.'/destination/other.txt', 'other content');
}

public function testMissingFile(): void
{
[$task, $state] = $this->createTask([
'filename' => $this->dir.'/missing.zip',
'destination' => $this->dir.'/destination',
]);

try {
$this->execute($task, $state, null);
self::fail('An \UnexpectedValueException should have been thrown');
} catch (\UnexpectedValueException $e) {
self::assertSame("File does not exists: '{$this->dir}/missing.zip'", $e->getMessage());
}
self::assertNull($state->getOutput());
}

public function testUnreadableFile(): void
{
chmod($this->dir.'/archive1.zip', 0o000);
if (is_readable($this->dir.'/archive1.zip')) {
self::markTestSkipped('Files are always readable by root');
}
[$task, $state] = $this->createTask([
'filename' => $this->dir.'/archive1.zip',
'destination' => $this->dir.'/destination',
]);

try {
$this->execute($task, $state, null);
self::fail('An \UnexpectedValueException should have been thrown');
} catch (\UnexpectedValueException $e) {
self::assertSame("File is not readable: '{$this->dir}/archive1.zip'", $e->getMessage());
}
}

public function testOptionsAreValidatedOnExecution(): void
{
// No exception at initialization: the options may come from the input
[$task, $state] = $this->createTask([]);

$this->expectException(MissingOptionsException::class);
$this->execute($task, $state, ['filename' => $this->dir.'/archive1.zip']);
}

public function testUndefinedOptionInInput(): void
{
[$task, $state] = $this->createTask([
'filename' => $this->dir.'/archive1.zip',
'destination' => $this->dir.'/destination',
]);

$this->expectException(UndefinedOptionsException::class);
$this->execute($task, $state, ['unknown' => 'value']);
}

public function testInvalidOptionType(): void
{
[$task, $state] = $this->createTask(['filename' => $this->dir.'/archive1.zip']);

$this->expectException(InvalidOptionsException::class);
$this->execute($task, $state, ['destination' => ['not', 'a', 'string']]);
}

/**
* @param array<string, mixed> $options
*
Expand Down
138 changes: 138 additions & 0 deletions tests/Task/ZipTaskTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException;
use Symfony\Component\OptionsResolver\Exception\MissingOptionsException;
use Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException;

#[CoversClass(ZipTask::class)]
class ZipTaskTest extends TestCase
Expand Down Expand Up @@ -128,6 +131,141 @@ public function testNonArrayInput(): void
}
}

public function testSingleFileAsString(): void
{
[$task, $state] = $this->createTask([
'filename' => $this->dir.'/archive.zip',
'files' => 'file1.txt',
'files_base_path' => $this->dir,
]);

$this->execute($task, $state, null);

self::assertSame(['file1.txt'], $this->getEntries($this->dir.'/archive.zip'));
}

public function testInputOverridesConfiguredOptions(): void
{
[$task, $state] = $this->createTask([
'filename' => $this->dir.'/configured.zip',
'files' => 'file1.txt',
'files_base_path' => $this->dir,
]);

self::assertSame($this->dir.'/input.zip', $this->execute($task, $state, ['filename' => $this->dir.'/input.zip']));
self::assertSame(['file1.txt'], $this->getEntries($this->dir.'/input.zip'));
self::assertFileDoesNotExist($this->dir.'/configured.zip');
}

public function testExistingArchiveIsOverwritten(): void
{
$zip = new \ZipArchive();
self::assertTrue($zip->open($this->dir.'/archive.zip', \ZipArchive::CREATE));
$zip->addFromString('old.txt', 'old');
$zip->close();
[$task, $state] = $this->createTask([
'filename' => $this->dir.'/archive.zip',
'files' => 'file1.txt',
'files_base_path' => $this->dir,
]);

$this->execute($task, $state, null);

self::assertSame(['file1.txt'], $this->getEntries($this->dir.'/archive.zip'));
}

public function testMissingFile(): void
{
[$task, $state] = $this->createTask([
'filename' => $this->dir.'/archive.zip',
'files' => 'missing.txt',
'files_base_path' => $this->dir,
]);

try {
$this->execute($task, $state, null);
self::fail('An \UnexpectedValueException should have been thrown');
} catch (\UnexpectedValueException $e) {
self::assertSame("File does not exists: '{$this->dir}/missing.txt'", $e->getMessage());
}
self::assertNull($state->getOutput());
}

public function testUnreadableFile(): void
{
chmod($this->dir.'/file1.txt', 0o000);
if (is_readable($this->dir.'/file1.txt')) {
self::markTestSkipped('Files are always readable by root');
}
[$task, $state] = $this->createTask([
'filename' => $this->dir.'/archive.zip',
'files' => 'file1.txt',
'files_base_path' => $this->dir,
]);

try {
$this->execute($task, $state, null);
self::fail('An \UnexpectedValueException should have been thrown');
} catch (\UnexpectedValueException $e) {
self::assertSame("File is not readable: '{$this->dir}/file1.txt'", $e->getMessage());
}
}

public function testDirectoryCannotBeAdded(): void
{
mkdir($this->dir.'/sub');
[$task, $state] = $this->createTask([
'filename' => $this->dir.'/archive.zip',
'files' => 'sub',
'files_base_path' => $this->dir,
]);

$this->expectException(\RuntimeException::class);
$this->execute($task, $state, null);
}

public function testOpenFailure(): void
{
mkdir($this->dir.'/archive.zip');
[$task, $state] = $this->createTask([
'filename' => $this->dir.'/archive.zip',
'files' => 'file1.txt',
'files_base_path' => $this->dir,
]);

try {
$this->execute($task, $state, null);
self::fail('A \RuntimeException should have been thrown');
} catch (\RuntimeException $e) {
self::assertStringStartsWith("Fail to open file {$this->dir}/archive.zip with code ", $e->getMessage());
}
}

public function testOptionsAreValidatedOnExecution(): void
{
// No exception at initialization: the options may come from the input
[$task, $state] = $this->createTask([]);

$this->expectException(MissingOptionsException::class);
$this->execute($task, $state, ['files' => 'file1.txt']);
}

public function testUndefinedOptionInInput(): void
{
[$task, $state] = $this->createTask(['filename' => $this->dir.'/archive.zip', 'files' => 'file1.txt']);

$this->expectException(UndefinedOptionsException::class);
$this->execute($task, $state, ['unknown' => 'value']);
}

public function testInvalidOptionType(): void
{
[$task, $state] = $this->createTask(['filename' => $this->dir.'/archive.zip']);

$this->expectException(InvalidOptionsException::class);
$this->execute($task, $state, ['files' => 12]);
}

/**
* @param array<string, mixed> $options
*
Expand Down
Loading