diff --git a/.github/actions/run-tests/run-occ.sh b/.github/actions/run-tests/run-occ.sh new file mode 100755 index 00000000..c6b53da1 --- /dev/null +++ b/.github/actions/run-tests/run-occ.sh @@ -0,0 +1,9 @@ +#! /bin/sh -e + +#set -x + +cd /nextcloud +php occ "$@" + +exit $? + diff --git a/phpunit.integration.xml b/phpunit.integration.xml index 0841ab06..1a711ad6 100755 --- a/phpunit.integration.xml +++ b/phpunit.integration.xml @@ -1,4 +1,9 @@ - + ./tests/Integration diff --git a/tests/Integration/Setup/Migrations/Version000000Date20210701093123Test.php b/tests/Integration/Setup/Migrations/Version000000Date20210701093123Test.php new file mode 100644 index 00000000..da8b8f03 --- /dev/null +++ b/tests/Integration/Setup/Migrations/Version000000Date20210701093123Test.php @@ -0,0 +1,169 @@ +container = $app->getContainer(); + + /** + * @var IDBConnection $db + */ + $this->db = $this->container->query(IDBConnection::class); + $this->assertIsObject($this->db); + /** + * @var SchemaWrapper $schema + */ + $schema = $this->container->query(SchemaWrapper::class); + $this->assertIsObject($schema); + + // undo all migrations of cookbook app + $qb = $this->db->getQueryBuilder(); + $numRows = $qb->delete('migrations') + ->where('app=:app') + ->setParameter('app', 'cookbook') + ->execute(); + $this->assertGreaterThan(0, $numRows); + + $schema->dropTable('cookbook_names'); + $this->assertFalse($schema->hasTable('cookbook_names')); + $schema->dropTable('cookbook_categories'); + $this->assertFalse($schema->hasTable('cookbook_categories')); + $schema->dropTable('cookbook_keywords'); + $this->assertFalse($schema->hasTable('cookbook_keywords')); + + $schema->performDropTableCalls(); + + // Reinstall app partially (just before the migration) + runOCCCommand(['migration:migrate', 'cookbook', '000000Date20210427082010']); + } + + /** + * @dataProvider dataProvider + * @runInSeparateProcess + */ + public function testRedundantEntriesInDB($data, $updatedUsers) { + // Add recipe dummy data from data provider + $qb = $this->db->getQueryBuilder(); + $qb->insert('cookbook_names') + ->values([ + 'recipe_id' => ':recipe', + 'user_id' => ':user', + 'name' => ':name', + ]); + $qb->setParameter('name', 'name of the recipe'); + foreach ($data as $d) { + $qb->setParameter('user', $d[0]); + $qb->setParameter('recipe', $d[1]); + + $this->assertEquals(1, $qb->execute()); + } + + // Initialize configuration values to track reindex timestamps + $current = time(); + + $qb = $this->db->getQueryBuilder(); + $qb->insert('preferences') + ->values([ + 'userid' => ':user', + 'appid' => ':appid', + 'configkey' => ':property', + 'configvalue' => ':value', + ]); + + $qb->setParameter('value', $current, IQueryBuilder::PARAM_STR); + $qb->setParameter('appid', 'cookbook'); + $qb->setParameter('property', 'last_index_update'); + + $users = array_unique(array_map(function ($x) {return $x[0];}, $data)); + foreach($users as $u){ + $qb->setParameter('user', $u); + $this->assertEquals(1, $qb->execute()); + } + + // Run the migration under test + runOCCCommand(['migration:migrate', 'cookbook', '000000Date20210701093123']); + + // Get the (updated) reindex timestamps + $qb = $this->db->getQueryBuilder(); + $qb->select('userid', 'configvalue') + ->from('preferences') + ->where( + 'appid = :appid', + 'configkey = :property' + ); + $qb->setParameter('appid', 'cookbook'); + $qb->setParameter('property', 'last_index_update'); + + $cursor = $qb->execute(); + $result = $cursor->fetchAll(); + + // Filter those entries from the configuration that were marked as to be reindexed + $result = array_filter($result, function ($x) use ($current) { return $x['configvalue'] < $current; }); + // Map the array to contain only the corresponding user names + $changedUsers = array_map(function ($x) { return $x['userid']; }, $result); + + // Sort the arrays to allow comparision of them + sort($changedUsers); + sort($updatedUsers); + + $this->assertEquals($updatedUsers, $changedUsers); + + $this->markTestIncomplete('Not yet implemented'); + } + + public function dataProvider() { + return [ + 'caseA' => [ + [ + ['alice', 123], + ['alice', 124], + ['bob', 125] + ], + [], + ], + 'caseB' => [ + [ + ['alice', 123], + ['alice', 124], + ['bob', 124], + ['bob', 125], + ], + [], + ], + 'caseC' => [ + [ + ['alice', 123], + ['alice', 124], + ['bob', 124], + ['bob', 124], + ['bob', 125], + ], + ['bob'] + ], + ]; + } +} diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 3d022ac9..f71388cd 100755 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -5,12 +5,31 @@ require_once __DIR__ . '/../../../tests/bootstrap.php'; // Fix for "Autoload path not allowed: .../cookbook/tests/testcase.php" \OC_App::loadApp('cookbook'); -function resetEnvironmentToBackup(string $name = 'default') { - exec("./.github/actions/run-tests/reset-from-container.sh $name 2>&1", $output, $ret); - if ($ret !== 0) { +function resetEnvironmentToBackup(string $name = 'default', bool $forceprint = false) { + $output = []; + $ret = -1; + exec("./.github/actions/run-tests/reset-from-container.sh $name 2>&1", $output, $ret); + if ($ret !== 0 || $forceprint) { echo "\nStandard output:\n"; print_r($output); echo "Return value: $ret\n"; - throw new Exception("Could not reset environment"); + if($ret !== 0) { + throw new Exception("Could not reset environment"); + } + } +} + +function runOCCCommand(array $args, bool $forceprint = false) { + $output = []; + $ret = -1; + $params = join(' ', array_map(function($x){return escapeshellarg($x);}, $args)); + exec("./.github/actions/run-tests/run-occ.sh $params 2>&1", $output, $ret); + if ($ret !== 0 || $forceprint) { + echo "\nStandard output:\n"; + print_r($output); + echo "Return value: $ret\n"; + if($ret !== 0) { + throw new Exception("Could not run OCC command"); + } } }