Skip to content

Commit

Permalink
WIP unit tests for tagging
Browse files Browse the repository at this point in the history
  • Loading branch information
matthewhilton committed Jul 21, 2024
1 parent dfe006f commit c4ba44f
Show file tree
Hide file tree
Showing 9 changed files with 231 additions and 25 deletions.
5 changes: 5 additions & 0 deletions classes/local/manager.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ public static function get_objectfs_config() {
$config->batchsize = 10000;
$config->useproxy = 0;
$config->deleteexternal = 0;
$config->enabletagging = false;

$config->filesystem = '';
$config->enablepresignedurls = 0;
Expand Down Expand Up @@ -329,6 +330,10 @@ public static function get_available_fs_list() {
* @return string
*/
public static function get_client_classname_from_fs($filesystem) {
// Unit tests.
if($filesystem == '\tool_objectfs\tests\test_file_system') {
return '\tool_objectfs\tests\test_client';
}
$clientclass = str_replace('_file_system', '', $filesystem);
return str_replace('tool_objectfs\\', 'tool_objectfs\\local\\store\\', $clientclass.'\\client');
}
Expand Down
4 changes: 0 additions & 4 deletions classes/local/store/object_file_system.php
Original file line number Diff line number Diff line change
Expand Up @@ -1154,8 +1154,4 @@ private function update_object(array $result): array {

return $result;
}

public function update_external_object_tags($contenthash, $tags) {
return;
}
}
2 changes: 1 addition & 1 deletion classes/local/tag/file_type_source.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ class file_type_source implements tag_source {

public const TYPE_BACKUP = 'backup';

public static function get_identifer(): string {
public static function get_identifier(): string {
return 'type';
}

Expand Down
9 changes: 4 additions & 5 deletions classes/local/tag/tag_manager.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,9 @@ public static function get_defined_tag_sources(): array {
*/
public static function is_tagging_enabled(): bool {
$enabledinconfig = !empty(get_config('tool_objectfs', 'taggingenabled'));

$client = manager::get_client(manager::get_objectfs_config());
$supportedbyfs = $client->supports_object_tagging();
$supportedbyfs = !empty($client) && $client->supports_object_tagging();

return $enabledinconfig && $supportedbyfs;
}
Expand All @@ -55,7 +56,7 @@ private static function gather_tags(string $contenthash): array {
continue;
}

$tags[$source->get_identifer()] = $val;
$tags[$source->get_identifier()] = $val;
}
return $tags;
}
Expand Down Expand Up @@ -150,8 +151,6 @@ public static function get_objects_needing_sync(int $limit) {
}

public static function replicate_local_to_external_tags_for_object(string $contenthash) {
global $DB;

// Take lock, to ensure tags are not updated while we sync them.
$lock = self::get_object_tag_lock($contenthash);

Expand Down Expand Up @@ -189,6 +188,6 @@ private static function mark_object_tag_status(string $contenthash, int $status)
}

private static function are_tags_different(array $a, array $b) {
return true; // TODO compare the keys and values to ensure they are exactly the same!
return $a !== $b;
}
}
4 changes: 1 addition & 3 deletions classes/local/tag/tag_source.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

namespace tool_objectfs\local\tag;

use stored_file;

interface tag_source {
/**
* Returns an unchanging identifier for this source.
Expand All @@ -12,7 +10,7 @@ interface tag_source {
* Must not exceed 128 chars. TODO unit test this.
* @return string
*/
public static function get_identifer(): string;
public static function get_identifier(): string;

/**
* Returns the value of this tag for the file with the given content hash.
Expand Down
4 changes: 2 additions & 2 deletions classes/task/update_object_tags.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@
class update_object_tags extends adhoc_task {
public function execute() {
if (!tag_manager::is_tagging_enabled()) {
mtrace("Tagging feature not available or supported, ignoring.");
mtrace("Tagging feature not enabled or supported by filesystem, exiting early.");
return;
}

// Find the object ids from the customdata.
// Find the object from the customdata.
$contenthash = $this->get_custom_data()->contenthash ?? null;

if (empty($contenthash)) {
Expand Down
5 changes: 5 additions & 0 deletions classes/tests/test_client.php
Original file line number Diff line number Diff line change
Expand Up @@ -157,5 +157,10 @@ public function test_permissions($testdelete) {
public function get_maximum_upload_size() {
return $this->maxupload;
}

public function supports_object_tagging(): bool {
global $CFG;
return $CFG->phpunit_objectfs_supports_object_tagging;
}
}

10 changes: 0 additions & 10 deletions classes/tests/test_tagging.php

This file was deleted.

213 changes: 213 additions & 0 deletions tests/local/tagging_test.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
<?php

namespace tool_objectfs\local;

use coding_exception;
use tool_objectfs\local\manager;
use tool_objectfs\local\tag\tag_manager;
use tool_objectfs\local\tag\tag_source;
use tool_objectfs\tests\testcase;

class tagging_test extends testcase {
public function test_get_defined_tag_sources() {
$sources = tag_manager::get_defined_tag_sources();
$this->assertIsArray($sources);

// Both AWS and Azure limit 10 tags per object, so ensure never more than 10 sources defined.
$this->assertLessThanOrEqual(10, count($sources));
}

public static function tag_source_provider(): array {
$sources = tag_manager::get_defined_tag_sources();
$tests = [];

foreach ($sources as $source) {
$tests[$source->get_identifier()] = [
'source' => $source
];
}

return $tests;
}

/**
* @dataProvider tag_source_provider
*/
public function test_tag_sources_identifier(tag_source $source) {
// Ensure tag source is < 128 chars, to fit AWS & Azure spec.
$count = strlen($source->get_identifier());
$this->assertLessThan(128, $count);
$this->assertGreaterThan(0, $count);
}

public static function is_tagging_enabled_provider(): array {
return [
'neither config nor fs supports' => [
'enabledinconfig' => false,
'supportedbyfs' => false,
'expected' => false,
],
'enabled in config but fs does not support' => [
'enabledinconfig' => true,
'supportedbyfs' => false,
'expected' => false,
],
'enabled in config and fs does support' => [
'enabledinconfig' => true,
'supportedbyfs' => true,
'expected' => true,
],
];
}

/**
* @dataProvider is_tagging_enabled_provider
*/
public function test_is_tagging_enabled(bool $enabledinconfig, bool $supportedbyfs, bool $expected) {
global $CFG;
// Set config.
set_config('taggingenabled', $enabledinconfig, 'tool_objectfs');

// Set supported by fs.
$config = manager::get_objectfs_config();
$config->taggingenabled = $enabledinconfig;
$config->enabletasks = true;
$config->filesystem = '\\tool_objectfs\\tests\\test_file_system';
manager::set_objectfs_config($config);
$CFG->phpunit_objectfs_supports_object_tagging = $supportedbyfs;

$this->assertEquals($expected, tag_manager::is_tagging_enabled());
}

public function test_query_local_tags() {
global $DB;

// Setup some fake tag data.
$DB->insert_records('tool_objectfs_object_tags', [
[
'contenthash' => 'abc123',
'tagkey' => 'test',
'tagvalue' => 'test',
'timemodified' => time()
],
[
'contenthash' => 'abc123',
'tagkey' => 'test2',
'tagvalue' => 'test2',
'timemodified' => time()
]
]);

$this->assertCount(2, tag_manager::query_local_tags('abc123'));
$this->assertCount(0, tag_manager::query_local_tags('doesnotexist'));
}

public function test_update_tags_if_necessary() {
global $DB;

// There should be no existing tags to begin with.
$this->assertEmpty(tag_manager::query_local_tags('test'));

// Update tags if necessary - should update the tags.
tag_manager::update_tags_if_necessary('test');

// Query tags - should be equal to number defined by the manager.
$expectedcount = count(tag_manager::get_defined_tag_sources());
$this->assertCount($expectedcount, tag_manager::query_local_tags('test'), 'Tags created match the number defined by manager');

// Note the timemodified of one of the tags.
$timemodified = $DB->get_field('tool_objectfs_object_tags', 'timemodified', ['contenthash' => 'test', 'tagkey' => tag_manager::get_defined_tag_sources()[0]->get_identifier()]);

// Running again, timemodified should not change.
$this->waitForSecond();
tag_manager::update_tags_if_necessary('test');

$newtimemodified = $DB->get_field('tool_objectfs_object_tags', 'timemodified', ['contenthash' => 'test', 'tagkey' => tag_manager::get_defined_tag_sources()[0]->get_identifier()]);
$this->assertEquals($timemodified, $newtimemodified, 'Tags timemodified must not change if not forced and values are not different');

// Except if it is forced.
$this->waitForSecond();
tag_manager::update_tags_if_necessary('test', true);

$timemodifiedafterforce = $DB->get_field('tool_objectfs_object_tags', 'timemodified', ['contenthash' => 'test', 'tagkey' => tag_manager::get_defined_tag_sources()[0]->get_identifier()]);
$this->assertNotEquals($timemodified, $timemodifiedafterforce, 'Forced tag update changed time modified');
}

public static function get_objects_needing_sync_provider(): array {
return [
'duplicated, needs sync' => [
'location' => OBJECT_LOCATION_DUPLICATED,
'status' => tag_manager::SYNC_STATUS_NEEDS_SYNC,
'expectedneedssync' => true,
],
'remote, needs sync' => [
'location' => OBJECT_LOCATION_EXTERNAL,
'status' => tag_manager::SYNC_STATUS_NEEDS_SYNC,
'expectedneedssync' => true,
],
'local, needs sync' => [
'location' => OBJECT_LOCATION_LOCAL,
'status' => tag_manager::SYNC_STATUS_NEEDS_SYNC,
'expectedneedssync' => false,
],
'duplicated, does not need sync' => [
'location' => OBJECT_LOCATION_DUPLICATED,
'status' => tag_manager::SYNC_STATUS_SYNC_NOT_REQUIRED,
'expectedneedssync' => false,
],
'local, does not need sync' => [
'location' => OBJECT_LOCATION_LOCAL,
'status' => tag_manager::SYNC_STATUS_SYNC_NOT_REQUIRED,
'expectedneedssync' => false,
],
];
}

/**
* @dataProvider get_objects_needing_sync_provider
*/
public function test_get_objects_needing_sync(int $location, int $syncstatus, bool $expectedneedssync) {
global $DB;

// Create the test object at the required location.
switch ($location) {
case OBJECT_LOCATION_DUPLICATED:
$object = $this->create_duplicated_object();
break;
case OBJECT_LOCATION_LOCAL:
$object = $this->create_local_object();
break;
case OBJECT_LOCATION_EXTERNAL:
$object = $this->create_remote_object();
break;
default:
throw new coding_exception("Object location not handled in test");
}

// Set the sync status.
$DB->set_field('tool_objectfs_objects', 'tagsyncstatus', $syncstatus, ['id' => $object->id]);

// Check if it is included in the list.
$needssync = tag_manager::get_objects_needing_sync(1);

if ($expectedneedssync) {
$this->assertContains($object->contenthash, $needssync);
} else {
$this->assertNotContains($object->contenthash, $needssync);
}
}

public function test_get_objects_needing_sync_limit() {
global $DB;

// Create two duplicated objects needing sync.
$object = $this->create_duplicated_object();
$DB->set_field('tool_objectfs_objects', 'tagsyncstatus', tag_manager::SYNC_STATUS_NEEDS_SYNC, ['id' => $object->id]);
$object = $this->create_remote_object();
$DB->set_field('tool_objectfs_objects', 'tagsyncstatus', tag_manager::SYNC_STATUS_NEEDS_SYNC, ['id' => $object->id]);

// Ensure a limit of 2 returns 2, and limit of 1 returns 1.
$this->assertCount(2, tag_manager::get_objects_needing_sync(2));
$this->assertCount(1, tag_manager::get_objects_needing_sync(1));
}
}

0 comments on commit c4ba44f

Please sign in to comment.