151 lines
6.3 KiB
PHP
151 lines
6.3 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace MeshVault\Tests\Integration;
|
|
|
|
use DomainException;
|
|
use InvalidArgumentException;
|
|
use MeshVault\Indexer\ExplicitImportGroupingAdapter;
|
|
use MeshVault\Indexer\ScannedFile;
|
|
use MeshVault\Repository\PdoCatalogRepository;
|
|
use MeshVault\Repository\PdoImportJobRepository;
|
|
use PHPUnit\Framework\TestCase;
|
|
|
|
final class ExplicitGroupingTest extends TestCase
|
|
{
|
|
private string $database;
|
|
private \PDO $pdo;
|
|
private PdoCatalogRepository $catalog;
|
|
private PdoImportJobRepository $jobs;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
$this->database = tempnam(sys_get_temp_dir(), 'grouping-');
|
|
$this->pdo = RepositoryDatabase::migrate($this->database);
|
|
$this->catalog = new PdoCatalogRepository($this->pdo);
|
|
$this->jobs = new PdoImportJobRepository($this->pdo);
|
|
self::assertTrue(class_exists(ExplicitImportGroupingAdapter::class), 'Explicit adapter must exist');
|
|
}
|
|
|
|
protected function tearDown(): void
|
|
{
|
|
RepositoryDatabase::cleanup($this->database);
|
|
}
|
|
|
|
public function testServicePersistsPerFileAndWildcardDecisionsAtomically(): void
|
|
{
|
|
$source = RepositoryDatabase::directory('explicit-source');
|
|
$library = RepositoryDatabase::directory('explicit-library');
|
|
try {
|
|
file_put_contents($source . '/a.stl', 'solid a');
|
|
file_put_contents($source . '/b.stl', 'solid b');
|
|
$adapter = new ExplicitImportGroupingAdapter($this->catalog, $this->jobs);
|
|
$service = new \MeshVault\Indexer\ImportService(
|
|
new \MeshVault\Indexer\FileEnumerator(
|
|
new \MeshVault\Indexer\Filesystem\LocalFilesystem(),
|
|
new \MeshVault\Indexer\Filesystem\PathSafety($source)
|
|
),
|
|
$this->jobs,
|
|
$adapter,
|
|
new \MeshVault\Indexer\SpaceChecker(),
|
|
$library
|
|
);
|
|
$context = ['*' => ['confirmed' => true, 'folder_path' => '/parts', 'model_name' => 'Part']];
|
|
$job = $service->import($source, ['stl'], $context);
|
|
$map = $adapter->recoverDecisions($job);
|
|
self::assertCount(2, $map);
|
|
self::assertSame($map, $adapter->recoverDecisions($service->import($source, ['stl'], $context)));
|
|
$before = (int) $this->pdo->query('SELECT count(*) FROM import_jobs')->fetchColumn();
|
|
try {
|
|
$service->import($source, ['stl'], ['a.stl' => $context['*']]);
|
|
self::fail('Incomplete grouping accepted');
|
|
} catch (\MeshVault\Indexer\ImportException) {
|
|
self::assertSame($before, (int) $this->pdo->query('SELECT count(*) FROM import_jobs')->fetchColumn());
|
|
}
|
|
} finally {
|
|
RepositoryDatabase::remove($source);
|
|
RepositoryDatabase::remove($library);
|
|
}
|
|
}
|
|
|
|
private function resolve(array $context): int
|
|
{
|
|
return (new ExplicitImportGroupingAdapter($this->catalog, $this->jobs))->modelFor(
|
|
'/source',
|
|
new ScannedFile('/source/a.stl', 'a.stl', 1, 'model/stl'),
|
|
$context
|
|
);
|
|
}
|
|
|
|
public function testConfirmationAndCompleteContextAreRequired(): void
|
|
{
|
|
foreach (
|
|
[[], ['confirmed' => true], ['confirmed' => 1, 'model_id' => 1],
|
|
['confirmed' => true, 'folder_path' => '/x'], ['confirmed' => true, 'model_id' => '1']] as $context
|
|
) {
|
|
try {
|
|
$this->resolve($context);
|
|
self::fail('Malformed or unconfirmed context accepted');
|
|
} catch (InvalidArgumentException) {
|
|
self::assertSame(0, (int) $this->pdo->query('SELECT count(*) FROM models')->fetchColumn());
|
|
}
|
|
}
|
|
}
|
|
|
|
public function testStableIdentityNewIdentityAndAmbiguity(): void
|
|
{
|
|
$context = ['confirmed' => true, 'folder_path' => '/parts', 'model_name' => 'Part'];
|
|
$id = $this->resolve($context);
|
|
self::assertSame($id, $this->resolve($context));
|
|
self::assertSame($id, $this->resolve(['model_id' => $id] + $context));
|
|
self::assertNotSame($id, $this->resolve(['create_new' => true] + $context));
|
|
$this->expectException(DomainException::class);
|
|
$this->resolve($context);
|
|
}
|
|
|
|
public function testIdentityMismatchAndMissingModelAreRejected(): void
|
|
{
|
|
$id = $this->catalog->createModel('/parts', 'Part');
|
|
foreach (
|
|
[['model_id' => $id, 'model_name' => 'Other'],
|
|
['model_id' => $id, 'folder_path' => '/other'], ['model_id' => 999999]] as $context
|
|
) {
|
|
try {
|
|
$this->resolve(['confirmed' => true] + $context);
|
|
self::fail('Conflicting identity accepted');
|
|
} catch (DomainException) {
|
|
self::assertTrue(true);
|
|
}
|
|
}
|
|
}
|
|
|
|
public function testDurableImmutableIdempotentDecisionsAndCallerRollback(): void
|
|
{
|
|
$id = $this->resolve(['confirmed' => true, 'folder_path' => '/parts', 'model_name' => 'Part']);
|
|
$job = $this->jobs->create('/source', [['source_path' => '/source/a.stl', 'size' => 1, 'model_id' => $id]]);
|
|
$adapter = new ExplicitImportGroupingAdapter($this->catalog, $this->jobs);
|
|
$decision = ['source_path' => '/source/a.stl', 'model_id' => $id, 'context' => ['confirmed' => true]];
|
|
$adapter->persistDecisions($job, [$decision]);
|
|
$adapter->persistDecisions($job, [$decision]);
|
|
$fresh = new ExplicitImportGroupingAdapter(
|
|
$this->catalog,
|
|
new PdoImportJobRepository(RepositoryDatabase::connect($this->database))
|
|
);
|
|
self::assertSame(['/source/a.stl' => $id], $fresh->recoverDecisions($job));
|
|
try {
|
|
$changed = array_replace($decision, ['context' => ['confirmed' => true, 'extra' => 1]]);
|
|
$adapter->persistDecisions($job, [$changed]);
|
|
self::fail('Decision changed');
|
|
} catch (DomainException) {
|
|
self::assertSame(['/source/a.stl' => $id], $adapter->recoverDecisions($job));
|
|
}
|
|
$this->pdo->beginTransaction();
|
|
$other = $this->jobs->create('/other', []);
|
|
$adapter->persistDecisions($other, [$decision]);
|
|
self::assertTrue($this->pdo->inTransaction());
|
|
$this->pdo->rollBack();
|
|
self::assertSame([], $adapter->recoverDecisions($other));
|
|
}
|
|
}
|