aboutsummaryrefslogtreecommitdiffstats
path: root/tests/lib/BackgroundJob
diff options
context:
space:
mode:
Diffstat (limited to 'tests/lib/BackgroundJob')
-rw-r--r--tests/lib/BackgroundJob/DummyJobList.php177
-rw-r--r--tests/lib/BackgroundJob/JobListTest.php341
-rw-r--r--tests/lib/BackgroundJob/JobTest.php66
-rw-r--r--tests/lib/BackgroundJob/QueuedJobTest.php41
-rw-r--r--tests/lib/BackgroundJob/TestJob.php39
-rw-r--r--tests/lib/BackgroundJob/TestParallelAwareJob.php39
-rw-r--r--tests/lib/BackgroundJob/TestTimedJobNew.php26
-rw-r--r--tests/lib/BackgroundJob/TimedJobTest.php57
8 files changed, 786 insertions, 0 deletions
diff --git a/tests/lib/BackgroundJob/DummyJobList.php b/tests/lib/BackgroundJob/DummyJobList.php
new file mode 100644
index 00000000000..717db52715f
--- /dev/null
+++ b/tests/lib/BackgroundJob/DummyJobList.php
@@ -0,0 +1,177 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2017-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace Test\BackgroundJob;
+
+use OC\BackgroundJob\JobList;
+use OCP\BackgroundJob\IJob;
+use OCP\BackgroundJob\Job;
+use OCP\Server;
+
+/**
+ * Class DummyJobList
+ *
+ * in memory job list for testing purposes
+ */
+class DummyJobList extends JobList {
+ /**
+ * @var IJob[]
+ */
+ private array $jobs = [];
+
+ /**
+ * @var bool[]
+ */
+ private array $reserved = [];
+
+ private int $last = 0;
+ private int $lastId = 0;
+
+ public function __construct() {
+ }
+
+ /**
+ * @param IJob|class-string<IJob> $job
+ * @param mixed $argument
+ */
+ public function add($job, $argument = null, ?int $firstCheck = null): void {
+ if (is_string($job)) {
+ /** @var IJob $job */
+ $job = Server::get($job);
+ }
+ $job->setArgument($argument);
+ $job->setId($this->lastId);
+ $this->lastId++;
+ if (!$this->has($job, null)) {
+ $this->jobs[] = $job;
+ }
+ }
+
+ public function scheduleAfter(string $job, int $runAfter, $argument = null): void {
+ $this->add($job, $argument, $runAfter);
+ }
+
+ /**
+ * @param IJob|string $job
+ * @param mixed $argument
+ */
+ public function remove($job, $argument = null): void {
+ foreach ($this->jobs as $index => $listJob) {
+ if (get_class($job) === get_class($listJob) && $job->getArgument() == $listJob->getArgument()) {
+ unset($this->jobs[$index]);
+ return;
+ }
+ }
+ }
+
+ public function removeById(int $id): void {
+ foreach ($this->jobs as $index => $listJob) {
+ if ($listJob->getId() === $id) {
+ unset($this->jobs[$index]);
+ return;
+ }
+ }
+ }
+
+ /**
+ * check if a job is in the list
+ *
+ * @param $job
+ * @param mixed $argument
+ * @return bool
+ */
+ public function has($job, $argument): bool {
+ return array_search($job, $this->jobs) !== false;
+ }
+
+ /**
+ * get all jobs in the list
+ *
+ * @return IJob[]
+ */
+ public function getAll(): array {
+ return $this->jobs;
+ }
+
+ public function getJobsIterator($job, ?int $limit, int $offset): array {
+ if ($job instanceof IJob) {
+ $jobClass = get_class($job);
+ } else {
+ $jobClass = $job;
+ }
+ return array_slice(
+ array_filter(
+ $this->jobs,
+ fn ($job) => ($jobClass === null) || (get_class($job) == $jobClass)
+ ),
+ $offset,
+ $limit
+ );
+ }
+
+ /**
+ * get the next job in the list
+ */
+ public function getNext(bool $onlyTimeSensitive = false, ?array $jobClasses = null): ?IJob {
+ if (count($this->jobs) > 0) {
+ if ($this->last < (count($this->jobs) - 1)) {
+ $i = $this->last + 1;
+ } else {
+ $i = 0;
+ }
+ return $this->jobs[$i];
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * set the job that was last ran
+ *
+ * @param Job $job
+ */
+ public function setLastJob(IJob $job): void {
+ $i = array_search($job, $this->jobs);
+ if ($i !== false) {
+ $this->last = $i;
+ } else {
+ $this->last = 0;
+ }
+ }
+
+ public function getById(int $id): ?IJob {
+ foreach ($this->jobs as $job) {
+ if ($job->getId() === $id) {
+ return $job;
+ }
+ }
+ return null;
+ }
+
+ public function getDetailsById(int $id): ?array {
+ return null;
+ }
+
+ public function setLastRun(IJob $job): void {
+ $job->setLastRun(time());
+ }
+
+ public function hasReservedJob(?string $className = null): bool {
+ return isset($this->reserved[$className ?? '']) && $this->reserved[$className ?? ''];
+ }
+
+ public function setHasReservedJob(?string $className, bool $hasReserved): void {
+ $this->reserved[$className ?? ''] = $hasReserved;
+ }
+
+ public function setExecutionTime(IJob $job, $timeTaken): void {
+ }
+
+ public function resetBackgroundJob(IJob $job): void {
+ }
+}
diff --git a/tests/lib/BackgroundJob/JobListTest.php b/tests/lib/BackgroundJob/JobListTest.php
new file mode 100644
index 00000000000..d816bf707e8
--- /dev/null
+++ b/tests/lib/BackgroundJob/JobListTest.php
@@ -0,0 +1,341 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace Test\BackgroundJob;
+
+use OC\BackgroundJob\JobList;
+use OCP\AppFramework\Utility\ITimeFactory;
+use OCP\BackgroundJob\IJob;
+use OCP\DB\QueryBuilder\IQueryBuilder;
+use OCP\IConfig;
+use OCP\IDBConnection;
+use OCP\Server;
+use Psr\Log\LoggerInterface;
+use Test\TestCase;
+
+/**
+ * Class JobList
+ *
+ * @group DB
+ * @package Test\BackgroundJob
+ */
+class JobListTest extends TestCase {
+ /** @var \OC\BackgroundJob\JobList */
+ protected $instance;
+
+ /** @var IDBConnection */
+ protected $connection;
+
+ /** @var IConfig|\PHPUnit\Framework\MockObject\MockObject */
+ protected $config;
+
+ /** @var ITimeFactory|\PHPUnit\Framework\MockObject\MockObject */
+ protected $timeFactory;
+ private bool $ran = false;
+
+ protected function setUp(): void {
+ parent::setUp();
+
+ $this->connection = Server::get(IDBConnection::class);
+ $this->clearJobsList();
+ $this->config = $this->createMock(IConfig::class);
+ $this->timeFactory = $this->createMock(ITimeFactory::class);
+ $this->instance = new JobList(
+ $this->connection,
+ $this->config,
+ $this->timeFactory,
+ Server::get(LoggerInterface::class),
+ );
+ }
+
+ protected function clearJobsList() {
+ $query = $this->connection->getQueryBuilder();
+ $query->delete('jobs');
+ $query->execute();
+ }
+
+ protected function getAllSorted() {
+ $iterator = $this->instance->getJobsIterator(null, null, 0);
+ $jobs = [];
+
+ foreach ($iterator as $job) {
+ $jobs[] = clone $job;
+ }
+
+ usort($jobs, function (IJob $job1, IJob $job2) {
+ return $job1->getId() - $job2->getId();
+ });
+
+ return $jobs;
+ }
+
+ public static function argumentProvider(): array {
+ return [
+ [null],
+ [false],
+ ['foobar'],
+ [12],
+ [[
+ 'asd' => 5,
+ 'foo' => 'bar'
+ ]]
+ ];
+ }
+
+ /**
+ * @param $argument
+ */
+ #[\PHPUnit\Framework\Attributes\DataProvider('argumentProvider')]
+ public function testAddRemove($argument): void {
+ $existingJobs = $this->getAllSorted();
+ $job = new TestJob();
+ $this->instance->add($job, $argument);
+
+ $jobs = $this->getAllSorted();
+
+ $this->assertCount(count($existingJobs) + 1, $jobs);
+ $addedJob = $jobs[count($jobs) - 1];
+ $this->assertInstanceOf('\Test\BackgroundJob\TestJob', $addedJob);
+ $this->assertEquals($argument, $addedJob->getArgument());
+
+ $this->instance->remove($job, $argument);
+
+ $jobs = $this->getAllSorted();
+ $this->assertEquals($existingJobs, $jobs);
+ }
+
+ /**
+ * @param $argument
+ */
+ #[\PHPUnit\Framework\Attributes\DataProvider('argumentProvider')]
+ public function testRemoveDifferentArgument($argument): void {
+ $existingJobs = $this->getAllSorted();
+ $job = new TestJob();
+ $this->instance->add($job, $argument);
+
+ $jobs = $this->getAllSorted();
+ $this->instance->remove($job, 10);
+ $jobs2 = $this->getAllSorted();
+
+ $this->assertEquals($jobs, $jobs2);
+
+ $this->instance->remove($job, $argument);
+
+ $jobs = $this->getAllSorted();
+ $this->assertEquals($existingJobs, $jobs);
+ }
+
+ /**
+ * @param $argument
+ */
+ #[\PHPUnit\Framework\Attributes\DataProvider('argumentProvider')]
+ public function testHas($argument): void {
+ $job = new TestJob();
+ $this->assertFalse($this->instance->has($job, $argument));
+ $this->instance->add($job, $argument);
+
+ $this->assertTrue($this->instance->has($job, $argument));
+
+ $this->instance->remove($job, $argument);
+
+ $this->assertFalse($this->instance->has($job, $argument));
+ }
+
+ /**
+ * @param $argument
+ */
+ #[\PHPUnit\Framework\Attributes\DataProvider('argumentProvider')]
+ public function testHasDifferentArgument($argument): void {
+ $job = new TestJob();
+ $this->instance->add($job, $argument);
+
+ $this->assertFalse($this->instance->has($job, 10));
+ }
+
+ protected function createTempJob($class,
+ $argument,
+ int $reservedTime = 0,
+ int $lastChecked = 0,
+ int $lastRun = 0): int {
+ if ($lastChecked === 0) {
+ $lastChecked = time();
+ }
+
+ $query = $this->connection->getQueryBuilder();
+ $query->insert('jobs')
+ ->values([
+ 'class' => $query->createNamedParameter($class),
+ 'argument' => $query->createNamedParameter($argument),
+ 'last_run' => $query->createNamedParameter($lastRun, IQueryBuilder::PARAM_INT),
+ 'last_checked' => $query->createNamedParameter($lastChecked, IQueryBuilder::PARAM_INT),
+ 'reserved_at' => $query->createNamedParameter($reservedTime, IQueryBuilder::PARAM_INT),
+ ]);
+ $query->executeStatement();
+ return $query->getLastInsertId();
+ }
+
+ public function testGetNext(): void {
+ $job = new TestJob();
+ $this->createTempJob(get_class($job), 1, 0, 12345);
+ $this->createTempJob(get_class($job), 2, 0, 12346);
+
+ $jobs = $this->getAllSorted();
+ $savedJob1 = $jobs[0];
+
+ $this->timeFactory->expects($this->atLeastOnce())
+ ->method('getTime')
+ ->willReturn(123456789);
+ $nextJob = $this->instance->getNext();
+
+ $this->assertEquals($savedJob1, $nextJob);
+ }
+
+ public function testGetNextSkipReserved(): void {
+ $job = new TestJob();
+ $this->createTempJob(get_class($job), 1, 123456789, 12345);
+ $this->createTempJob(get_class($job), 2, 0, 12346);
+
+ $this->timeFactory->expects($this->atLeastOnce())
+ ->method('getTime')
+ ->willReturn(123456789);
+ $nextJob = $this->instance->getNext();
+
+ $this->assertEquals(get_class($job), get_class($nextJob));
+ $this->assertEquals(2, $nextJob->getArgument());
+ }
+
+ public function testGetNextSkipTimed(): void {
+ $job = new TestTimedJobNew($this->timeFactory);
+ $jobId = $this->createTempJob(get_class($job), 1, 123456789, 12345, 123456789 - 5);
+ $this->timeFactory->expects(self::atLeastOnce())
+ ->method('getTime')
+ ->willReturn(123456789);
+
+ $nextJob = $this->instance->getNext();
+
+ self::assertNull($nextJob);
+ $job = $this->instance->getById($jobId);
+ self::assertInstanceOf(TestTimedJobNew::class, $job);
+ self::assertEquals(123456789 - 5, $job->getLastRun());
+ }
+
+ public function testGetNextSkipNonExisting(): void {
+ $job = new TestJob();
+ $this->createTempJob('\OC\Non\Existing\Class', 1, 0, 12345);
+ $this->createTempJob(get_class($job), 2, 0, 12346);
+
+ $this->timeFactory->expects($this->atLeastOnce())
+ ->method('getTime')
+ ->willReturn(123456789);
+ $nextJob = $this->instance->getNext();
+
+ $this->assertEquals(get_class($job), get_class($nextJob));
+ $this->assertEquals(2, $nextJob->getArgument());
+ }
+
+ /**
+ * @param $argument
+ */
+ #[\PHPUnit\Framework\Attributes\DataProvider('argumentProvider')]
+ public function testGetById($argument): void {
+ $job = new TestJob();
+ $this->instance->add($job, $argument);
+
+ $jobs = $this->getAllSorted();
+
+ $addedJob = $jobs[count($jobs) - 1];
+
+ $this->assertEquals($addedJob, $this->instance->getById($addedJob->getId()));
+ }
+
+ public function testSetLastRun(): void {
+ $job = new TestJob();
+ $this->instance->add($job);
+
+ $jobs = $this->getAllSorted();
+
+ $addedJob = $jobs[count($jobs) - 1];
+
+ $timeStart = time();
+ $this->instance->setLastRun($addedJob);
+ $timeEnd = time();
+
+ $addedJob = $this->instance->getById($addedJob->getId());
+
+ $this->assertGreaterThanOrEqual($timeStart, $addedJob->getLastRun());
+ $this->assertLessThanOrEqual($timeEnd, $addedJob->getLastRun());
+ }
+
+ public function testHasReservedJobs(): void {
+ $this->clearJobsList();
+
+ $this->timeFactory->expects($this->atLeastOnce())
+ ->method('getTime')
+ ->willReturn(123456789);
+
+ $job = new TestJob($this->timeFactory, $this, function (): void {
+ });
+
+ $job2 = new TestJob($this->timeFactory, $this, function (): void {
+ });
+
+ $this->instance->add($job, 1);
+ $this->instance->add($job2, 2);
+
+ $this->assertCount(2, iterator_to_array($this->instance->getJobsIterator(null, 10, 0)));
+
+ $this->assertFalse($this->instance->hasReservedJob());
+ $this->assertFalse($this->instance->hasReservedJob(TestJob::class));
+
+ $job = $this->instance->getNext();
+ $this->assertNotNull($job);
+ $this->assertTrue($this->instance->hasReservedJob());
+ $this->assertTrue($this->instance->hasReservedJob(TestJob::class));
+ $job = $this->instance->getNext();
+ $this->assertNotNull($job);
+ $this->assertTrue($this->instance->hasReservedJob());
+ $this->assertTrue($this->instance->hasReservedJob(TestJob::class));
+ }
+
+ public function testHasReservedJobsAndParallelAwareJob(): void {
+ $this->clearJobsList();
+
+ $this->timeFactory->expects($this->atLeastOnce())
+ ->method('getTime')
+ ->willReturnCallback(function () use (&$time) {
+ return time();
+ });
+
+ $job = new TestParallelAwareJob($this->timeFactory, $this, function (): void {
+ });
+
+ $job2 = new TestParallelAwareJob($this->timeFactory, $this, function (): void {
+ });
+
+ $this->instance->add($job, 1);
+ $this->instance->add($job2, 2);
+
+ $this->assertCount(2, iterator_to_array($this->instance->getJobsIterator(null, 10, 0)));
+
+ $this->assertFalse($this->instance->hasReservedJob());
+ $this->assertFalse($this->instance->hasReservedJob(TestParallelAwareJob::class));
+
+ $job = $this->instance->getNext();
+ $this->assertNotNull($job);
+ $this->assertTrue($this->instance->hasReservedJob());
+ $this->assertTrue($this->instance->hasReservedJob(TestParallelAwareJob::class));
+ $job = $this->instance->getNext();
+ $this->assertNull($job); // Job doesn't allow parallel runs
+ }
+
+ public function markRun() {
+ $this->ran = true;
+ }
+}
diff --git a/tests/lib/BackgroundJob/JobTest.php b/tests/lib/BackgroundJob/JobTest.php
new file mode 100644
index 00000000000..b67059f0380
--- /dev/null
+++ b/tests/lib/BackgroundJob/JobTest.php
@@ -0,0 +1,66 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2017-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace Test\BackgroundJob;
+
+use OCP\AppFramework\Utility\ITimeFactory;
+use OCP\Server;
+use Psr\Log\LoggerInterface;
+
+class JobTest extends \Test\TestCase {
+ private $run = false;
+ private ITimeFactory $timeFactory;
+ private LoggerInterface $logger;
+
+ protected function setUp(): void {
+ parent::setUp();
+ $this->run = false;
+ $this->timeFactory = Server::get(ITimeFactory::class);
+ $this->logger = $this->createMock(LoggerInterface::class);
+
+ \OC::$server->registerService(LoggerInterface::class, fn ($c) => $this->logger);
+ }
+
+ public function testRemoveAfterException(): void {
+ $jobList = new DummyJobList();
+ $e = new \Exception();
+ $job = new TestJob($this->timeFactory, $this, function () use ($e): void {
+ throw $e;
+ });
+ $jobList->add($job);
+
+ $this->logger->expects($this->once())
+ ->method('error');
+
+ $this->assertCount(1, $jobList->getAll());
+ $job->start($jobList);
+ $this->assertTrue($this->run);
+ $this->assertCount(1, $jobList->getAll());
+ }
+
+ public function testRemoveAfterError(): void {
+ $jobList = new DummyJobList();
+ $job = new TestJob($this->timeFactory, $this, function (): void {
+ $test = null;
+ $test->someMethod();
+ });
+ $jobList->add($job);
+
+ $this->logger->expects($this->once())
+ ->method('error');
+
+ $this->assertCount(1, $jobList->getAll());
+ $job->start($jobList);
+ $this->assertTrue($this->run);
+ $this->assertCount(1, $jobList->getAll());
+ }
+
+ public function markRun() {
+ $this->run = true;
+ }
+}
diff --git a/tests/lib/BackgroundJob/QueuedJobTest.php b/tests/lib/BackgroundJob/QueuedJobTest.php
new file mode 100644
index 00000000000..1c0946ff2f2
--- /dev/null
+++ b/tests/lib/BackgroundJob/QueuedJobTest.php
@@ -0,0 +1,41 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2018-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace Test\BackgroundJob;
+
+use OCP\AppFramework\Utility\ITimeFactory;
+use OCP\BackgroundJob\QueuedJob;
+use OCP\Server;
+
+class TestQueuedJobNew extends QueuedJob {
+ public bool $ran = false;
+
+ public function run($argument) {
+ $this->ran = true;
+ }
+}
+
+class QueuedJobTest extends \Test\TestCase {
+ private DummyJobList $jobList;
+
+ protected function setUp(): void {
+ parent::setUp();
+
+ $this->jobList = new DummyJobList();
+ }
+
+ public function testJobShouldBeRemovedNew(): void {
+ $job = new TestQueuedJobNew(Server::get(ITimeFactory::class));
+ $job->setId(42);
+ $this->jobList->add($job);
+
+ $this->assertTrue($this->jobList->has($job, null));
+ $job->start($this->jobList);
+ $this->assertTrue($job->ran);
+ }
+}
diff --git a/tests/lib/BackgroundJob/TestJob.php b/tests/lib/BackgroundJob/TestJob.php
new file mode 100644
index 00000000000..ac18530ac7f
--- /dev/null
+++ b/tests/lib/BackgroundJob/TestJob.php
@@ -0,0 +1,39 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2020-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace Test\BackgroundJob;
+
+use OCP\AppFramework\Utility\ITimeFactory;
+use OCP\BackgroundJob\Job;
+use OCP\Server;
+
+class TestJob extends Job {
+ /**
+ * @var callable $callback
+ */
+ private $callback;
+
+ /**
+ * @param JobTest $testCase
+ * @param callable $callback
+ */
+ public function __construct(
+ ?ITimeFactory $time = null,
+ private $testCase = null,
+ $callback = null,
+ ) {
+ parent::__construct($time ?? Server::get(ITimeFactory::class));
+ $this->callback = $callback;
+ }
+
+ public function run($argument) {
+ $this->testCase->markRun();
+ $callback = $this->callback;
+ $callback($argument);
+ }
+}
diff --git a/tests/lib/BackgroundJob/TestParallelAwareJob.php b/tests/lib/BackgroundJob/TestParallelAwareJob.php
new file mode 100644
index 00000000000..6efb1a1fd8d
--- /dev/null
+++ b/tests/lib/BackgroundJob/TestParallelAwareJob.php
@@ -0,0 +1,39 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace Test\BackgroundJob;
+
+use OCP\AppFramework\Utility\ITimeFactory;
+use OCP\BackgroundJob\Job;
+use OCP\Server;
+
+class TestParallelAwareJob extends Job {
+ /**
+ * @var callable $callback
+ */
+ private $callback;
+
+ /**
+ * @param JobTest $testCase
+ * @param callable $callback
+ */
+ public function __construct(
+ ?ITimeFactory $time = null,
+ private $testCase = null,
+ $callback = null,
+ ) {
+ parent::__construct($time ?? Server::get(ITimeFactory::class));
+ $this->setAllowParallelRuns(false);
+ $this->callback = $callback;
+ }
+
+ public function run($argument) {
+ $this->testCase->markRun();
+ $callback = $this->callback;
+ $callback($argument);
+ }
+}
diff --git a/tests/lib/BackgroundJob/TestTimedJobNew.php b/tests/lib/BackgroundJob/TestTimedJobNew.php
new file mode 100644
index 00000000000..a40c4033655
--- /dev/null
+++ b/tests/lib/BackgroundJob/TestTimedJobNew.php
@@ -0,0 +1,26 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+namespace Test\BackgroundJob;
+
+use OCP\AppFramework\Utility\ITimeFactory;
+use OCP\BackgroundJob\TimedJob;
+
+class TestTimedJobNew extends TimedJob {
+ public bool $ran = false;
+
+ public function __construct(ITimeFactory $timeFactory) {
+ parent::__construct($timeFactory);
+ $this->setInterval(10);
+ }
+
+ public function run($argument) {
+ $this->ran = true;
+ }
+}
diff --git a/tests/lib/BackgroundJob/TimedJobTest.php b/tests/lib/BackgroundJob/TimedJobTest.php
new file mode 100644
index 00000000000..d56240eb75e
--- /dev/null
+++ b/tests/lib/BackgroundJob/TimedJobTest.php
@@ -0,0 +1,57 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2018-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace Test\BackgroundJob;
+
+use OCP\AppFramework\Utility\ITimeFactory;
+use OCP\Server;
+
+class TimedJobTest extends \Test\TestCase {
+ private DummyJobList $jobList;
+ private ITimeFactory $time;
+
+ protected function setUp(): void {
+ parent::setUp();
+
+ $this->jobList = new DummyJobList();
+ $this->time = Server::get(ITimeFactory::class);
+ }
+
+ public function testShouldRunAfterIntervalNew(): void {
+ $job = new TestTimedJobNew($this->time);
+ $job->setId(42);
+ $this->jobList->add($job);
+
+ $job->setLastRun(time() - 12);
+ $job->start($this->jobList);
+ $this->assertTrue($job->ran);
+ }
+
+ public function testShouldNotRunWithinIntervalNew(): void {
+ $job = new TestTimedJobNew($this->time);
+ $job->setId(42);
+ $this->jobList->add($job);
+
+ $job->setLastRun(time() - 5);
+ $job->start($this->jobList);
+ $this->assertFalse($job->ran);
+ }
+
+ public function testShouldNotTwiceNew(): void {
+ $job = new TestTimedJobNew($this->time);
+ $job->setId(42);
+ $this->jobList->add($job);
+
+ $job->setLastRun(time() - 15);
+ $job->start($this->jobList);
+ $this->assertTrue($job->ran);
+ $job->ran = false;
+ $job->start($this->jobList);
+ $this->assertFalse($job->ran);
+ }
+}