aboutsummaryrefslogtreecommitdiffstats
path: root/apps/settings/tests/SetupChecks
diff options
context:
space:
mode:
Diffstat (limited to 'apps/settings/tests/SetupChecks')
-rw-r--r--apps/settings/tests/SetupChecks/AppDirsWithDifferentOwnerTest.php102
-rw-r--r--apps/settings/tests/SetupChecks/CodeIntegrityTest.php134
-rw-r--r--apps/settings/tests/SetupChecks/DataDirectoryProtectedTest.php117
-rw-r--r--apps/settings/tests/SetupChecks/ForwardedForHeadersTest.php119
-rw-r--r--apps/settings/tests/SetupChecks/LoggingLevelTest.php76
-rw-r--r--apps/settings/tests/SetupChecks/OcxProvicersTest.php151
-rw-r--r--apps/settings/tests/SetupChecks/PhpDefaultCharsetTest.php48
-rw-r--r--apps/settings/tests/SetupChecks/PhpOutputBufferingTest.php44
-rw-r--r--apps/settings/tests/SetupChecks/SecurityHeadersTest.php196
-rw-r--r--apps/settings/tests/SetupChecks/SupportedDatabaseTest.php56
-rw-r--r--apps/settings/tests/SetupChecks/TaskProcessingPickupSpeedTest.php73
-rw-r--r--apps/settings/tests/SetupChecks/WellKnownUrlsTest.php215
12 files changed, 1262 insertions, 69 deletions
diff --git a/apps/settings/tests/SetupChecks/AppDirsWithDifferentOwnerTest.php b/apps/settings/tests/SetupChecks/AppDirsWithDifferentOwnerTest.php
new file mode 100644
index 00000000000..423f932dcf5
--- /dev/null
+++ b/apps/settings/tests/SetupChecks/AppDirsWithDifferentOwnerTest.php
@@ -0,0 +1,102 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace OCA\Settings\Tests\SetupChecks;
+
+use OCA\Settings\SetupChecks\AppDirsWithDifferentOwner;
+use OCP\IL10N;
+use Test\TestCase;
+
+class AppDirsWithDifferentOwnerTest extends TestCase {
+ private IL10N $l10n;
+ private AppDirsWithDifferentOwner $check;
+
+ /**
+ * Holds a list of directories created during tests.
+ *
+ * @var array
+ */
+ private $dirsToRemove = [];
+
+ protected function setUp(): void {
+ parent::setUp();
+
+ $this->l10n = $this->createMock(IL10N::class);
+ $this->l10n->expects($this->any())
+ ->method('t')
+ ->willReturnCallback(function ($message, array $replace) {
+ return vsprintf($message, $replace);
+ });
+ $this->check = new AppDirsWithDifferentOwner(
+ $this->l10n,
+ );
+ }
+
+ /**
+ * Setups a temp directory and some subdirectories.
+ * Then calls the 'getAppDirsWithDifferentOwner' method.
+ * The result is expected to be empty since
+ * there are no directories with different owners than the current user.
+ *
+ * @return void
+ */
+ public function testAppDirectoryOwnersOk(): void {
+ $tempDir = tempnam(sys_get_temp_dir(), 'apps') . 'dir';
+ mkdir($tempDir);
+ mkdir($tempDir . DIRECTORY_SEPARATOR . 'app1');
+ mkdir($tempDir . DIRECTORY_SEPARATOR . 'app2');
+ $this->dirsToRemove[] = $tempDir . DIRECTORY_SEPARATOR . 'app1';
+ $this->dirsToRemove[] = $tempDir . DIRECTORY_SEPARATOR . 'app2';
+ $this->dirsToRemove[] = $tempDir;
+ \OC::$APPSROOTS = [
+ [
+ 'path' => $tempDir,
+ 'url' => '/apps',
+ 'writable' => true,
+ ],
+ ];
+ $this->assertSame(
+ [],
+ $this->invokePrivate($this->check, 'getAppDirsWithDifferentOwner', [posix_getuid()])
+ );
+ }
+
+ /**
+ * Calls the check for a none existing app root that is marked as not writable.
+ * It's expected that no error happens since the check shouldn't apply.
+ *
+ * @return void
+ */
+ public function testAppDirectoryOwnersNotWritable(): void {
+ $tempDir = tempnam(sys_get_temp_dir(), 'apps') . 'dir';
+ \OC::$APPSROOTS = [
+ [
+ 'path' => $tempDir,
+ 'url' => '/apps',
+ 'writable' => false,
+ ],
+ ];
+ $this->assertSame(
+ [],
+ $this->invokePrivate($this->check, 'getAppDirsWithDifferentOwner', [posix_getuid()])
+ );
+ }
+
+ /**
+ * Removes directories created during tests.
+ *
+ * @after
+ * @return void
+ */
+ public function removeTestDirectories() {
+ foreach ($this->dirsToRemove as $dirToRemove) {
+ rmdir($dirToRemove);
+ }
+ $this->dirsToRemove = [];
+ }
+}
diff --git a/apps/settings/tests/SetupChecks/CodeIntegrityTest.php b/apps/settings/tests/SetupChecks/CodeIntegrityTest.php
new file mode 100644
index 00000000000..4dd54a644f5
--- /dev/null
+++ b/apps/settings/tests/SetupChecks/CodeIntegrityTest.php
@@ -0,0 +1,134 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace OCA\Settings\Tests\SetupChecks;
+
+use OC\IntegrityCheck\Checker;
+use OCA\Settings\SetupChecks\CodeIntegrity;
+use OCP\IL10N;
+use OCP\IURLGenerator;
+use OCP\SetupCheck\SetupResult;
+use PHPUnit\Framework\MockObject\MockObject;
+use Test\TestCase;
+
+class CodeIntegrityTest extends TestCase {
+
+ private IL10N&MockObject $l10n;
+ private IURLGenerator&MockObject $urlGenerator;
+ private Checker&MockObject $checker;
+
+ protected function setUp(): void {
+ parent::setUp();
+
+ $this->l10n = $this->createMock(IL10N::class);
+ $this->l10n->expects($this->any())
+ ->method('t')
+ ->willReturnCallback(function ($message, array $replace) {
+ return vsprintf($message, $replace);
+ });
+ $this->urlGenerator = $this->createMock(IURLGenerator::class);
+ $this->checker = $this->createMock(Checker::class);
+ }
+
+ public function testSkipOnDisabled(): void {
+ $this->checker->expects($this->atLeastOnce())
+ ->method('isCodeCheckEnforced')
+ ->willReturn(false);
+
+ $check = new CodeIntegrity(
+ $this->l10n,
+ $this->urlGenerator,
+ $this->checker,
+ );
+ $this->assertEquals(SetupResult::INFO, $check->run()->getSeverity());
+ }
+
+ public function testSuccessOnEmptyResults(): void {
+ $this->checker->expects($this->atLeastOnce())
+ ->method('isCodeCheckEnforced')
+ ->willReturn(true);
+ $this->checker->expects($this->atLeastOnce())
+ ->method('getResults')
+ ->willReturn([]);
+ $this->checker->expects(($this->atLeastOnce()))
+ ->method('hasPassedCheck')
+ ->willReturn(true);
+
+ $check = new CodeIntegrity(
+ $this->l10n,
+ $this->urlGenerator,
+ $this->checker,
+ );
+ $this->assertEquals(SetupResult::SUCCESS, $check->run()->getSeverity());
+ }
+
+ public function testCheckerIsReRunWithoutResults(): void {
+ $this->checker->expects($this->atLeastOnce())
+ ->method('isCodeCheckEnforced')
+ ->willReturn(true);
+ $this->checker->expects($this->atLeastOnce())
+ ->method('getResults')
+ ->willReturn(null);
+ $this->checker->expects(($this->atLeastOnce()))
+ ->method('hasPassedCheck')
+ ->willReturn(true);
+
+ // This is important and must be called
+ $this->checker->expects($this->once())
+ ->method('runInstanceVerification');
+
+ $check = new CodeIntegrity(
+ $this->l10n,
+ $this->urlGenerator,
+ $this->checker,
+ );
+ $this->assertEquals(SetupResult::SUCCESS, $check->run()->getSeverity());
+ }
+
+ public function testCheckerIsNotReReInAdvance(): void {
+ $this->checker->expects($this->atLeastOnce())
+ ->method('isCodeCheckEnforced')
+ ->willReturn(true);
+ $this->checker->expects($this->atLeastOnce())
+ ->method('getResults')
+ ->willReturn(['mocked']);
+ $this->checker->expects(($this->atLeastOnce()))
+ ->method('hasPassedCheck')
+ ->willReturn(true);
+
+ // There are results thus this must never be called
+ $this->checker->expects($this->never())
+ ->method('runInstanceVerification');
+
+ $check = new CodeIntegrity(
+ $this->l10n,
+ $this->urlGenerator,
+ $this->checker,
+ );
+ $this->assertEquals(SetupResult::SUCCESS, $check->run()->getSeverity());
+ }
+
+ public function testErrorOnMissingIntegrity(): void {
+ $this->checker->expects($this->atLeastOnce())
+ ->method('isCodeCheckEnforced')
+ ->willReturn(true);
+ $this->checker->expects($this->atLeastOnce())
+ ->method('getResults')
+ ->willReturn(['mocked']);
+ $this->checker->expects(($this->atLeastOnce()))
+ ->method('hasPassedCheck')
+ ->willReturn(false);
+
+ $check = new CodeIntegrity(
+ $this->l10n,
+ $this->urlGenerator,
+ $this->checker,
+ );
+ $this->assertEquals(SetupResult::ERROR, $check->run()->getSeverity());
+ }
+}
diff --git a/apps/settings/tests/SetupChecks/DataDirectoryProtectedTest.php b/apps/settings/tests/SetupChecks/DataDirectoryProtectedTest.php
new file mode 100644
index 00000000000..c20c78c6e16
--- /dev/null
+++ b/apps/settings/tests/SetupChecks/DataDirectoryProtectedTest.php
@@ -0,0 +1,117 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace OCA\Settings\Tests\SetupChecks;
+
+use OCA\Settings\SetupChecks\DataDirectoryProtected;
+use OCP\Http\Client\IClientService;
+use OCP\Http\Client\IResponse;
+use OCP\IConfig;
+use OCP\IL10N;
+use OCP\IURLGenerator;
+use OCP\SetupCheck\SetupResult;
+use PHPUnit\Framework\MockObject\MockObject;
+use Psr\Log\LoggerInterface;
+use Test\TestCase;
+
+class DataDirectoryProtectedTest extends TestCase {
+ private IL10N&MockObject $l10n;
+ private IConfig&MockObject $config;
+ private IURLGenerator&MockObject $urlGenerator;
+ private IClientService&MockObject $clientService;
+ private LoggerInterface&MockObject $logger;
+ private DataDirectoryProtected&MockObject $setupcheck;
+
+ protected function setUp(): void {
+ parent::setUp();
+
+ $this->l10n = $this->createMock(IL10N::class);
+ $this->l10n->expects($this->any())
+ ->method('t')
+ ->willReturnCallback(function ($message, array $replace) {
+ return vsprintf($message, $replace);
+ });
+
+ $this->config = $this->createMock(IConfig::class);
+ $this->urlGenerator = $this->createMock(IURLGenerator::class);
+ $this->clientService = $this->createMock(IClientService::class);
+ $this->logger = $this->createMock(LoggerInterface::class);
+
+ $this->setupcheck = $this->getMockBuilder(DataDirectoryProtected::class)
+ ->onlyMethods(['runRequest'])
+ ->setConstructorArgs([
+ $this->l10n,
+ $this->config,
+ $this->urlGenerator,
+ $this->clientService,
+ $this->logger,
+ ])
+ ->getMock();
+ }
+
+ #[\PHPUnit\Framework\Attributes\DataProvider('dataTestStatusCode')]
+ public function testStatusCode(array $status, string $expected, bool $hasBody): void {
+ $responses = array_map(function ($state) use ($hasBody) {
+ $response = $this->createMock(IResponse::class);
+ $response->expects($this->any())->method('getStatusCode')->willReturn($state);
+ $response->expects(($this->atMost(1)))->method('getBody')->willReturn($hasBody ? '# Nextcloud data directory' : 'something else');
+ return $response;
+ }, $status);
+
+ $this->setupcheck
+ ->expects($this->once())
+ ->method('runRequest')
+ ->will($this->generate($responses));
+
+ $this->config
+ ->expects($this->once())
+ ->method('getSystemValueString')
+ ->willReturn('');
+
+ $result = $this->setupcheck->run();
+ $this->assertEquals($expected, $result->getSeverity());
+ }
+
+ public static function dataTestStatusCode(): array {
+ return [
+ 'success: forbidden access' => [[403], SetupResult::SUCCESS, true],
+ 'success: forbidden access with redirect' => [[200], SetupResult::SUCCESS, false],
+ 'error: can access' => [[200], SetupResult::ERROR, true],
+ 'error: one forbidden one can access' => [[403, 200], SetupResult::ERROR, true],
+ 'warning: connection issue' => [[], SetupResult::WARNING, true],
+ ];
+ }
+
+ public function testNoResponse(): void {
+ $response = $this->createMock(IResponse::class);
+ $response->expects($this->any())->method('getStatusCode')->willReturn(200);
+
+ $this->setupcheck
+ ->expects($this->once())
+ ->method('runRequest')
+ ->will($this->generate([]));
+
+ $this->config
+ ->expects($this->once())
+ ->method('getSystemValueString')
+ ->willReturn('');
+
+ $result = $this->setupcheck->run();
+ $this->assertEquals(SetupResult::WARNING, $result->getSeverity());
+ $this->assertMatchesRegularExpression('/^Could not check/', $result->getDescription());
+ }
+
+ /**
+ * Helper function creates a nicer interface for mocking Generator behavior
+ */
+ protected function generate(array $yield_values) {
+ return $this->returnCallback(function () use ($yield_values) {
+ yield from $yield_values;
+ });
+ }
+}
diff --git a/apps/settings/tests/SetupChecks/ForwardedForHeadersTest.php b/apps/settings/tests/SetupChecks/ForwardedForHeadersTest.php
new file mode 100644
index 00000000000..9b4878b45cc
--- /dev/null
+++ b/apps/settings/tests/SetupChecks/ForwardedForHeadersTest.php
@@ -0,0 +1,119 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace OCA\Settings\Tests\SetupChecks;
+
+use OCA\Settings\SetupChecks\ForwardedForHeaders;
+use OCP\IConfig;
+use OCP\IL10N;
+use OCP\IRequest;
+use OCP\IURLGenerator;
+use OCP\SetupCheck\SetupResult;
+use Test\TestCase;
+
+class ForwardedForHeadersTest extends TestCase {
+ private IL10N $l10n;
+ private IConfig $config;
+ private IURLGenerator $urlGenerator;
+ private IRequest $request;
+ private ForwardedForHeaders $check;
+
+ protected function setUp(): void {
+ parent::setUp();
+
+ $this->l10n = $this->createMock(IL10N::class);
+ $this->l10n->expects($this->any())
+ ->method('t')
+ ->willReturnCallback(function ($message, array $replace) {
+ return vsprintf($message, $replace);
+ });
+ $this->config = $this->getMockBuilder(IConfig::class)->getMock();
+ $this->urlGenerator = $this->getMockBuilder(IURLGenerator::class)->getMock();
+ $this->request = $this->getMockBuilder(IRequest::class)->getMock();
+ $this->check = new ForwardedForHeaders(
+ $this->l10n,
+ $this->config,
+ $this->urlGenerator,
+ $this->request,
+ );
+ }
+
+ #[\PHPUnit\Framework\Attributes\DataProvider('dataForwardedForHeadersWorking')]
+ public function testForwardedForHeadersWorking(array $trustedProxies, string $remoteAddrNotForwarded, string $remoteAddr, string $result): void {
+ $this->config->expects($this->once())
+ ->method('getSystemValue')
+ ->with('trusted_proxies', [])
+ ->willReturn($trustedProxies);
+ $this->request->expects($this->atLeastOnce())
+ ->method('getHeader')
+ ->willReturnMap([
+ ['REMOTE_ADDR', $remoteAddrNotForwarded],
+ ['X-Forwarded-Host', '']
+ ]);
+ $this->request->expects($this->any())
+ ->method('getRemoteAddress')
+ ->willReturn($remoteAddr);
+
+ $this->assertEquals(
+ $result,
+ $this->check->run()->getSeverity()
+ );
+ }
+
+ public static function dataForwardedForHeadersWorking(): array {
+ return [
+ // description => trusted proxies, getHeader('REMOTE_ADDR'), getRemoteAddr, expected result
+ 'no trusted proxies' => [[], '2.2.2.2', '2.2.2.2', SetupResult::SUCCESS],
+ 'trusted proxy, remote addr not trusted proxy' => [['1.1.1.1'], '2.2.2.2', '2.2.2.2', SetupResult::SUCCESS],
+ 'trusted proxy, remote addr is trusted proxy, x-forwarded-for working' => [['1.1.1.1'], '1.1.1.1', '2.2.2.2', SetupResult::SUCCESS],
+ 'trusted proxy, remote addr is trusted proxy, x-forwarded-for not set' => [['1.1.1.1'], '1.1.1.1', '1.1.1.1', SetupResult::WARNING],
+ ];
+ }
+
+ public function testForwardedHostPresentButTrustedProxiesNotAnArray(): void {
+ $this->config->expects($this->once())
+ ->method('getSystemValue')
+ ->with('trusted_proxies', [])
+ ->willReturn('1.1.1.1');
+ $this->request->expects($this->atLeastOnce())
+ ->method('getHeader')
+ ->willReturnMap([
+ ['REMOTE_ADDR', '1.1.1.1'],
+ ['X-Forwarded-Host', 'nextcloud.test']
+ ]);
+ $this->request->expects($this->any())
+ ->method('getRemoteAddress')
+ ->willReturn('1.1.1.1');
+
+ $this->assertEquals(
+ SetupResult::ERROR,
+ $this->check->run()->getSeverity()
+ );
+ }
+
+ public function testForwardedHostPresentButTrustedProxiesEmpty(): void {
+ $this->config->expects($this->once())
+ ->method('getSystemValue')
+ ->with('trusted_proxies', [])
+ ->willReturn([]);
+ $this->request->expects($this->atLeastOnce())
+ ->method('getHeader')
+ ->willReturnMap([
+ ['REMOTE_ADDR', '1.1.1.1'],
+ ['X-Forwarded-Host', 'nextcloud.test']
+ ]);
+ $this->request->expects($this->any())
+ ->method('getRemoteAddress')
+ ->willReturn('1.1.1.1');
+
+ $this->assertEquals(
+ SetupResult::ERROR,
+ $this->check->run()->getSeverity()
+ );
+ }
+}
diff --git a/apps/settings/tests/SetupChecks/LoggingLevelTest.php b/apps/settings/tests/SetupChecks/LoggingLevelTest.php
new file mode 100644
index 00000000000..67224e11e3a
--- /dev/null
+++ b/apps/settings/tests/SetupChecks/LoggingLevelTest.php
@@ -0,0 +1,76 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace OCA\Settings\Tests\SetupChecks;
+
+use OCA\Settings\SetupChecks\LoggingLevel;
+use OCP\IConfig;
+use OCP\IL10N;
+use OCP\ILogger;
+use OCP\IURLGenerator;
+use OCP\SetupCheck\SetupResult;
+use PHPUnit\Framework\MockObject\MockObject;
+use Psr\Log\LogLevel;
+use Test\TestCase;
+
+class LoggingLevelTest extends TestCase {
+ private IL10N&MockObject $l10n;
+ private IConfig&MockObject $config;
+ private IURLGenerator&MockObject $urlGenerator;
+
+ protected function setUp(): void {
+ parent::setUp();
+
+ $this->l10n = $this->createMock(IL10N::class);
+ $this->l10n->expects($this->any())
+ ->method('t')
+ ->willReturnCallback(function ($message, array $replace) {
+ return vsprintf($message, $replace);
+ });
+ $this->config = $this->createMock(IConfig::class);
+ $this->urlGenerator = $this->createMock(IURLGenerator::class);
+ }
+
+ public static function dataRun(): array {
+ return [
+ [ILogger::INFO, SetupResult::SUCCESS],
+ [ILogger::WARN, SetupResult::SUCCESS],
+ [ILogger::ERROR, SetupResult::SUCCESS],
+ [ILogger::FATAL, SetupResult::SUCCESS],
+
+ // Debug is valid but will result in an warning
+ [ILogger::DEBUG, SetupResult::WARNING],
+
+ // negative - invalid range
+ [-1, SetupResult::ERROR],
+ // string value instead of number
+ ['1', SetupResult::ERROR],
+ // random string value
+ ['error', SetupResult::ERROR],
+ // PSR logger value
+ [LogLevel::ALERT, SetupResult::ERROR],
+ // out of range
+ [ILogger::FATAL + 1, SetupResult::ERROR],
+ ];
+ }
+
+ #[\PHPUnit\Framework\Attributes\DataProvider('dataRun')]
+ public function testRun(string|int $value, string $expected): void {
+ $this->urlGenerator->method('linkToDocs')->willReturn('admin-logging');
+
+ $this->config->expects(self::once())
+ ->method('getSystemValue')
+ ->with('loglevel', ILogger::WARN)
+ ->willReturn($value);
+
+ $check = new LoggingLevel($this->l10n, $this->config, $this->urlGenerator);
+
+ $result = $check->run();
+ $this->assertEquals($expected, $result->getSeverity());
+ }
+}
diff --git a/apps/settings/tests/SetupChecks/OcxProvicersTest.php b/apps/settings/tests/SetupChecks/OcxProvicersTest.php
new file mode 100644
index 00000000000..8e5a2c1b88b
--- /dev/null
+++ b/apps/settings/tests/SetupChecks/OcxProvicersTest.php
@@ -0,0 +1,151 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace OCA\Settings\Tests\SetupChecks;
+
+use OCA\Settings\SetupChecks\OcxProviders;
+use OCP\Http\Client\IClientService;
+use OCP\Http\Client\IResponse;
+use OCP\IConfig;
+use OCP\IL10N;
+use OCP\IURLGenerator;
+use OCP\SetupCheck\SetupResult;
+use PHPUnit\Framework\MockObject\MockObject;
+use Psr\Log\LoggerInterface;
+use Test\TestCase;
+
+class OcxProvicersTest extends TestCase {
+ private IL10N|MockObject $l10n;
+ private IConfig|MockObject $config;
+ private IURLGenerator|MockObject $urlGenerator;
+ private IClientService|MockObject $clientService;
+ private LoggerInterface|MockObject $logger;
+ private OcxProviders|MockObject $setupcheck;
+
+ protected function setUp(): void {
+ parent::setUp();
+
+ $this->l10n = $this->createMock(IL10N::class);
+ $this->l10n->expects($this->any())
+ ->method('t')
+ ->willReturnCallback(function ($message, array $replace) {
+ return vsprintf($message, $replace);
+ });
+
+ $this->config = $this->createMock(IConfig::class);
+ $this->urlGenerator = $this->createMock(IURLGenerator::class);
+ $this->clientService = $this->createMock(IClientService::class);
+ $this->logger = $this->createMock(LoggerInterface::class);
+
+ $this->setupcheck = $this->getMockBuilder(OcxProviders::class)
+ ->onlyMethods(['runRequest'])
+ ->setConstructorArgs([
+ $this->l10n,
+ $this->config,
+ $this->urlGenerator,
+ $this->clientService,
+ $this->logger,
+ ])
+ ->getMock();
+ }
+
+ public function testSuccess(): void {
+ $response = $this->createMock(IResponse::class);
+ $response->expects($this->any())->method('getStatusCode')->willReturn(200);
+
+ $this->setupcheck
+ ->expects($this->exactly(2))
+ ->method('runRequest')
+ ->willReturnOnConsecutiveCalls($this->generate([$response]), $this->generate([$response]));
+
+ $result = $this->setupcheck->run();
+ $this->assertEquals(SetupResult::SUCCESS, $result->getSeverity());
+ }
+
+ public function testLateSuccess(): void {
+ $response1 = $this->createMock(IResponse::class);
+ $response1->expects($this->exactly(3))->method('getStatusCode')->willReturnOnConsecutiveCalls(404, 500, 200);
+ $response2 = $this->createMock(IResponse::class);
+ $response2->expects($this->any())->method('getStatusCode')->willReturnOnConsecutiveCalls(200);
+
+ $this->setupcheck
+ ->expects($this->exactly(2))
+ ->method('runRequest')
+ ->willReturnOnConsecutiveCalls($this->generate([$response1, $response1, $response1]), $this->generate([$response2])); // only one response out of two
+
+ $result = $this->setupcheck->run();
+ $this->assertEquals(SetupResult::SUCCESS, $result->getSeverity());
+ }
+
+ public function testNoResponse(): void {
+ $response = $this->createMock(IResponse::class);
+ $response->expects($this->any())->method('getStatusCode')->willReturn(200);
+
+ $this->setupcheck
+ ->expects($this->exactly(2))
+ ->method('runRequest')
+ ->willReturnOnConsecutiveCalls($this->generate([]), $this->generate([])); // No responses
+
+ $result = $this->setupcheck->run();
+ $this->assertEquals(SetupResult::WARNING, $result->getSeverity());
+ $this->assertMatchesRegularExpression('/^Could not check/', $result->getDescription());
+ }
+
+ public function testPartialResponse(): void {
+ $response = $this->createMock(IResponse::class);
+ $response->expects($this->any())->method('getStatusCode')->willReturn(200);
+
+ $this->setupcheck
+ ->expects($this->exactly(2))
+ ->method('runRequest')
+ ->willReturnOnConsecutiveCalls($this->generate([$response]), $this->generate([])); // only one response out of two
+
+ $result = $this->setupcheck->run();
+ $this->assertEquals(SetupResult::WARNING, $result->getSeverity());
+ $this->assertMatchesRegularExpression('/^Could not check/', $result->getDescription());
+ }
+
+ public function testInvalidResponse(): void {
+ $response = $this->createMock(IResponse::class);
+ $response->expects($this->any())->method('getStatusCode')->willReturn(404);
+
+ $this->setupcheck
+ ->expects($this->exactly(2))
+ ->method('runRequest')
+ ->willReturnOnConsecutiveCalls($this->generate([$response]), $this->generate([$response])); // only one response out of two
+
+ $result = $this->setupcheck->run();
+ $this->assertEquals(SetupResult::WARNING, $result->getSeverity());
+ $this->assertMatchesRegularExpression('/^Your web server is not properly set up/', $result->getDescription());
+ }
+
+ public function testPartialInvalidResponse(): void {
+ $response1 = $this->createMock(IResponse::class);
+ $response1->expects($this->any())->method('getStatusCode')->willReturnOnConsecutiveCalls(200);
+ $response2 = $this->createMock(IResponse::class);
+ $response2->expects($this->any())->method('getStatusCode')->willReturnOnConsecutiveCalls(404);
+
+ $this->setupcheck
+ ->expects($this->exactly(2))
+ ->method('runRequest')
+ ->willReturnOnConsecutiveCalls($this->generate([$response1]), $this->generate([$response2]));
+
+ $result = $this->setupcheck->run();
+ $this->assertEquals(SetupResult::WARNING, $result->getSeverity());
+ $this->assertMatchesRegularExpression('/^Your web server is not properly set up/', $result->getDescription());
+ }
+
+ /**
+ * Helper function creates a nicer interface for mocking Generator behavior
+ */
+ protected function generate(array $yield_values) {
+ return $this->returnCallback(function () use ($yield_values) {
+ yield from $yield_values;
+ });
+ }
+}
diff --git a/apps/settings/tests/SetupChecks/PhpDefaultCharsetTest.php b/apps/settings/tests/SetupChecks/PhpDefaultCharsetTest.php
index eac671a0e13..3722346219a 100644
--- a/apps/settings/tests/SetupChecks/PhpDefaultCharsetTest.php
+++ b/apps/settings/tests/SetupChecks/PhpDefaultCharsetTest.php
@@ -3,42 +3,42 @@
declare(strict_types=1);
/**
- * @copyright Copyright (c) 2020 Daniel Kesselberg <mail@danielkesselberg.de>
- *
- * @author Daniel Kesselberg <mail@danielkesselberg.de>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
-namespace OCA\Settings\Tests;
+namespace OCA\Settings\Tests\SetupChecks;
use OCA\Settings\SetupChecks\PhpDefaultCharset;
+use OCP\IL10N;
+use OCP\SetupCheck\SetupResult;
+use PHPUnit\Framework\MockObject\MockObject;
use Test\TestCase;
class PhpDefaultCharsetTest extends TestCase {
+ /** @var IL10N|MockObject */
+ private $l10n;
+
+ protected function setUp(): void {
+ parent::setUp();
+
+ $this->l10n = $this->createMock(IL10N::class);
+ $this->l10n->expects($this->any())
+ ->method('t')
+ ->willReturnCallback(function ($message, array $replace) {
+ return vsprintf($message, $replace);
+ });
+ }
+
public function testPass(): void {
- $check = new PhpDefaultCharset();
- $this->assertTrue($check->run());
+ $check = new PhpDefaultCharset($this->l10n);
+ $this->assertEquals(SetupResult::SUCCESS, $check->run()->getSeverity());
}
public function testFail(): void {
ini_set('default_charset', 'ISO-8859-15');
- $check = new PhpDefaultCharset();
- $this->assertFalse($check->run());
+ $check = new PhpDefaultCharset($this->l10n);
+ $this->assertEquals(SetupResult::WARNING, $check->run()->getSeverity());
ini_restore('default_charset');
}
diff --git a/apps/settings/tests/SetupChecks/PhpOutputBufferingTest.php b/apps/settings/tests/SetupChecks/PhpOutputBufferingTest.php
index 9e0301dcc12..de509347044 100644
--- a/apps/settings/tests/SetupChecks/PhpOutputBufferingTest.php
+++ b/apps/settings/tests/SetupChecks/PhpOutputBufferingTest.php
@@ -3,39 +3,39 @@
declare(strict_types=1);
/**
- * @copyright Copyright (c) 2020 Daniel Kesselberg <mail@danielkesselberg.de>
- *
- * @author Daniel Kesselberg <mail@danielkesselberg.de>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
-namespace OCA\Settings\Tests;
+namespace OCA\Settings\Tests\SetupChecks;
use OCA\Settings\SetupChecks\PhpOutputBuffering;
+use OCP\IL10N;
+use OCP\SetupCheck\SetupResult;
+use PHPUnit\Framework\MockObject\MockObject;
use Test\TestCase;
class PhpOutputBufferingTest extends TestCase {
+ /** @var IL10N|MockObject */
+ private $l10n;
+
+ protected function setUp(): void {
+ parent::setUp();
+
+ $this->l10n = $this->createMock(IL10N::class);
+ $this->l10n->expects($this->any())
+ ->method('t')
+ ->willReturnCallback(function ($message, array $replace) {
+ return vsprintf($message, $replace);
+ });
+ }
+
/*
* output_buffer is PHP_INI_PERDIR and cannot changed at runtime.
* Run this test with -d output_buffering=1 to validate the fail case.
*/
public function testPass(): void {
- $check = new PhpOutputBuffering();
- $this->assertTrue($check->run());
+ $check = new PhpOutputBuffering($this->l10n);
+ $this->assertEquals(SetupResult::SUCCESS, $check->run()->getSeverity());
}
}
diff --git a/apps/settings/tests/SetupChecks/SecurityHeadersTest.php b/apps/settings/tests/SetupChecks/SecurityHeadersTest.php
new file mode 100644
index 00000000000..1f75907d427
--- /dev/null
+++ b/apps/settings/tests/SetupChecks/SecurityHeadersTest.php
@@ -0,0 +1,196 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace OCA\Settings\Tests\SetupChecks;
+
+use OCA\Settings\SetupChecks\SecurityHeaders;
+use OCP\Http\Client\IClientService;
+use OCP\Http\Client\IResponse;
+use OCP\IConfig;
+use OCP\IL10N;
+use OCP\IURLGenerator;
+use OCP\SetupCheck\SetupResult;
+use PHPUnit\Framework\MockObject\MockObject;
+use Psr\Log\LoggerInterface;
+use Test\TestCase;
+
+class SecurityHeadersTest extends TestCase {
+ private IL10N&MockObject $l10n;
+ private IConfig&MockObject $config;
+ private IURLGenerator&MockObject $urlGenerator;
+ private IClientService&MockObject $clientService;
+ private LoggerInterface&MockObject $logger;
+ private SecurityHeaders&MockObject $setupcheck;
+
+ protected function setUp(): void {
+ parent::setUp();
+
+ $this->l10n = $this->createMock(IL10N::class);
+ $this->l10n->expects($this->any())
+ ->method('t')
+ ->willReturnCallback(function ($message, array $replace) {
+ return vsprintf($message, $replace);
+ });
+
+ $this->config = $this->createMock(IConfig::class);
+ $this->urlGenerator = $this->createMock(IURLGenerator::class);
+ $this->clientService = $this->createMock(IClientService::class);
+ $this->logger = $this->createMock(LoggerInterface::class);
+
+ $this->setupcheck = $this->getMockBuilder(SecurityHeaders::class)
+ ->onlyMethods(['runRequest'])
+ ->setConstructorArgs([
+ $this->l10n,
+ $this->config,
+ $this->urlGenerator,
+ $this->clientService,
+ $this->logger,
+ ])
+ ->getMock();
+ }
+
+ public function testInvalidStatusCode(): void {
+ $this->setupResponse(500, []);
+
+ $result = $this->setupcheck->run();
+ $this->assertMatchesRegularExpression('/^Could not check that your web server serves security headers correctly/', $result->getDescription());
+ $this->assertEquals(SetupResult::WARNING, $result->getSeverity());
+ }
+
+ public function testAllHeadersMissing(): void {
+ $this->setupResponse(200, []);
+
+ $result = $this->setupcheck->run();
+ $this->assertMatchesRegularExpression('/^Some headers are not set correctly on your instance/', $result->getDescription());
+ $this->assertEquals(SetupResult::WARNING, $result->getSeverity());
+ }
+
+ public function testSomeHeadersMissing(): void {
+ $this->setupResponse(
+ 200,
+ [
+ 'X-Robots-Tag' => 'noindex, nofollow',
+ 'X-Frame-Options' => 'SAMEORIGIN',
+ 'Strict-Transport-Security' => 'max-age=15768000;preload',
+ 'X-Permitted-Cross-Domain-Policies' => 'none',
+ 'Referrer-Policy' => 'no-referrer',
+ ]
+ );
+
+ $result = $this->setupcheck->run();
+ $this->assertEquals(
+ "Some headers are not set correctly on your instance\n- The `X-Content-Type-Options` HTTP header is not set to `nosniff`. This is a potential security or privacy risk, as it is recommended to adjust this setting accordingly.\n",
+ $result->getDescription()
+ );
+ $this->assertEquals(SetupResult::WARNING, $result->getSeverity());
+ }
+
+ public static function dataSuccess(): array {
+ return [
+ // description => modifiedHeaders
+ 'basic' => [[]],
+ 'no-space-in-x-robots' => [['X-Robots-Tag' => 'noindex,nofollow']],
+ 'strict-origin-when-cross-origin' => [['Referrer-Policy' => 'strict-origin-when-cross-origin']],
+ 'referrer-no-referrer-when-downgrade' => [['Referrer-Policy' => 'no-referrer-when-downgrade']],
+ 'referrer-strict-origin' => [['Referrer-Policy' => 'strict-origin']],
+ 'referrer-strict-origin-when-cross-origin' => [['Referrer-Policy' => 'strict-origin-when-cross-origin']],
+ 'referrer-same-origin' => [['Referrer-Policy' => 'same-origin']],
+ 'hsts-minimum' => [['Strict-Transport-Security' => 'max-age=15552000']],
+ 'hsts-include-subdomains' => [['Strict-Transport-Security' => 'max-age=99999999; includeSubDomains']],
+ 'hsts-include-subdomains-preload' => [['Strict-Transport-Security' => 'max-age=99999999; preload; includeSubDomains']],
+ ];
+ }
+
+ #[\PHPUnit\Framework\Attributes\DataProvider('dataSuccess')]
+ public function testSuccess(array $headers): void {
+ $headers = array_merge(
+ [
+ 'X-Content-Type-Options' => 'nosniff',
+ 'X-Robots-Tag' => 'noindex, nofollow',
+ 'X-Frame-Options' => 'SAMEORIGIN',
+ 'Strict-Transport-Security' => 'max-age=15768000',
+ 'X-Permitted-Cross-Domain-Policies' => 'none',
+ 'Referrer-Policy' => 'no-referrer',
+ ],
+ $headers
+ );
+ $this->setupResponse(
+ 200,
+ $headers
+ );
+
+ $result = $this->setupcheck->run();
+ $this->assertEquals(
+ 'Your server is correctly configured to send security headers.',
+ $result->getDescription()
+ );
+ $this->assertEquals(SetupResult::SUCCESS, $result->getSeverity());
+ }
+
+ public static function dataFailure(): array {
+ return [
+ // description => modifiedHeaders
+ 'x-robots-none' => [['X-Robots-Tag' => 'none'], "- The `X-Robots-Tag` HTTP header is not set to `noindex,nofollow`. This is a potential security or privacy risk, as it is recommended to adjust this setting accordingly.\n"],
+ 'referrer-origin' => [['Referrer-Policy' => 'origin'], "- The `Referrer-Policy` HTTP header is not set to `no-referrer`, `no-referrer-when-downgrade`, `strict-origin`, `strict-origin-when-cross-origin` or `same-origin`. This can leak referer information. See the {w3c-recommendation}.\n"],
+ 'referrer-origin-when-cross-origin' => [['Referrer-Policy' => 'origin-when-cross-origin'], "- The `Referrer-Policy` HTTP header is not set to `no-referrer`, `no-referrer-when-downgrade`, `strict-origin`, `strict-origin-when-cross-origin` or `same-origin`. This can leak referer information. See the {w3c-recommendation}.\n"],
+ 'referrer-unsafe-url' => [['Referrer-Policy' => 'unsafe-url'], "- The `Referrer-Policy` HTTP header is not set to `no-referrer`, `no-referrer-when-downgrade`, `strict-origin`, `strict-origin-when-cross-origin` or `same-origin`. This can leak referer information. See the {w3c-recommendation}.\n"],
+ 'hsts-missing' => [['Strict-Transport-Security' => ''], "- The `Strict-Transport-Security` HTTP header is not set (should be at least `15552000` seconds). For enhanced security, it is recommended to enable HSTS.\n"],
+ 'hsts-too-low' => [['Strict-Transport-Security' => 'max-age=15551999'], "- The `Strict-Transport-Security` HTTP header is not set to at least `15552000` seconds (current value: `15551999`). For enhanced security, it is recommended to use a long HSTS policy.\n"],
+ 'hsts-malformed' => [['Strict-Transport-Security' => 'iAmABogusHeader342'], "- The `Strict-Transport-Security` HTTP header is malformed: `iAmABogusHeader342`. For enhanced security, it is recommended to enable HSTS.\n"],
+ ];
+ }
+
+ #[\PHPUnit\Framework\Attributes\DataProvider('dataFailure')]
+ public function testFailure(array $headers, string $msg): void {
+ $headers = array_merge(
+ [
+ 'X-Content-Type-Options' => 'nosniff',
+ 'X-Robots-Tag' => 'noindex, nofollow',
+ 'X-Frame-Options' => 'SAMEORIGIN',
+ 'Strict-Transport-Security' => 'max-age=15768000',
+ 'X-Permitted-Cross-Domain-Policies' => 'none',
+ 'Referrer-Policy' => 'no-referrer',
+ ],
+ $headers
+ );
+ $this->setupResponse(
+ 200,
+ $headers
+ );
+
+ $result = $this->setupcheck->run();
+ $this->assertEquals(
+ 'Some headers are not set correctly on your instance' . "\n$msg",
+ $result->getDescription()
+ );
+ $this->assertEquals(SetupResult::WARNING, $result->getSeverity());
+ }
+
+ protected function setupResponse(int $statuscode, array $headers): void {
+ $response = $this->createMock(IResponse::class);
+ $response->expects($this->atLeastOnce())->method('getStatusCode')->willReturn($statuscode);
+ $response->expects($this->any())->method('getHeader')
+ ->willReturnCallback(
+ fn (string $header): string => $headers[$header] ?? ''
+ );
+
+ $this->setupcheck
+ ->expects($this->atLeastOnce())
+ ->method('runRequest')
+ ->willReturnOnConsecutiveCalls($this->generate([$response]));
+ }
+
+ /**
+ * Helper function creates a nicer interface for mocking Generator behavior
+ */
+ protected function generate(array $yield_values) {
+ return $this->returnCallback(function () use ($yield_values) {
+ yield from $yield_values;
+ });
+ }
+}
diff --git a/apps/settings/tests/SetupChecks/SupportedDatabaseTest.php b/apps/settings/tests/SetupChecks/SupportedDatabaseTest.php
index 35c27769e78..6c75df47aa0 100644
--- a/apps/settings/tests/SetupChecks/SupportedDatabaseTest.php
+++ b/apps/settings/tests/SetupChecks/SupportedDatabaseTest.php
@@ -3,39 +3,49 @@
declare(strict_types=1);
/**
- * @copyright Copyright (c) 2021 Morris Jobke <hey@morrisjobke.de>
- *
- * @author Morris Jobke <hey@morrisjobke.de>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
-namespace OCA\Settings\Tests;
+namespace OCA\Settings\Tests\SetupChecks;
use OCA\Settings\SetupChecks\SupportedDatabase;
+use OCP\IDBConnection;
use OCP\IL10N;
+use OCP\IURLGenerator;
+use OCP\Server;
+use OCP\SetupCheck\SetupResult;
use Test\TestCase;
/**
* @group DB
*/
class SupportedDatabaseTest extends TestCase {
+ private IL10N $l10n;
+ private IUrlGenerator $urlGenerator;
+ private IDBConnection $connection;
+
+ private SupportedDatabase $check;
+
+ protected function setUp(): void {
+ parent::setUp();
+
+ $this->l10n = $this->createMock(IL10N::class);
+ $this->urlGenerator = $this->createMock(IURLGenerator::class);
+ $this->connection = Server::get(IDBConnection::class);
+
+ $this->check = new SupportedDatabase(
+ $this->l10n,
+ $this->urlGenerator,
+ Server::get(IDBConnection::class)
+ );
+ }
+
public function testPass(): void {
- $l10n = $this->getMockBuilder(IL10N::class)->getMock();
- $check = new SupportedDatabase($l10n, \OC::$server->getDatabaseConnection());
- $this->assertTrue($check->run());
+ if ($this->connection->getDatabaseProvider() === IDBConnection::PLATFORM_SQLITE) {
+ /** SQlite always gets a warning */
+ $this->assertEquals(SetupResult::WARNING, $this->check->run()->getSeverity());
+ } else {
+ $this->assertContains($this->check->run()->getSeverity(), [SetupResult::SUCCESS, SetupResult::INFO]);
+ }
}
}
diff --git a/apps/settings/tests/SetupChecks/TaskProcessingPickupSpeedTest.php b/apps/settings/tests/SetupChecks/TaskProcessingPickupSpeedTest.php
new file mode 100644
index 00000000000..6375d9f6e7f
--- /dev/null
+++ b/apps/settings/tests/SetupChecks/TaskProcessingPickupSpeedTest.php
@@ -0,0 +1,73 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace OCA\Settings\Tests;
+
+use OCA\Settings\SetupChecks\TaskProcessingPickupSpeed;
+use OCP\AppFramework\Utility\ITimeFactory;
+use OCP\IL10N;
+use OCP\SetupCheck\SetupResult;
+use OCP\TaskProcessing\IManager;
+use OCP\TaskProcessing\Task;
+use Test\TestCase;
+
+class TaskProcessingPickupSpeedTest extends TestCase {
+ private IL10N $l10n;
+ private ITimeFactory $timeFactory;
+ private IManager $taskProcessingManager;
+
+ private TaskProcessingPickupSpeed $check;
+
+ protected function setUp(): void {
+ parent::setUp();
+
+ $this->l10n = $this->getMockBuilder(IL10N::class)->getMock();
+ $this->timeFactory = $this->getMockBuilder(ITimeFactory::class)->getMock();
+ $this->taskProcessingManager = $this->getMockBuilder(IManager::class)->getMock();
+
+ $this->check = new TaskProcessingPickupSpeed(
+ $this->l10n,
+ $this->taskProcessingManager,
+ $this->timeFactory,
+ );
+ }
+
+ public function testPass(): void {
+ $tasks = [];
+ for ($i = 0; $i < 100; $i++) {
+ $task = new Task('test', ['test' => 'test'], 'settings', 'user' . $i);
+ $task->setStartedAt(0);
+ if ($i < 15) {
+ $task->setScheduledAt(60 * 5); // 15% get 5mins
+ } else {
+ $task->setScheduledAt(60); // the rest gets 1min
+ }
+ $tasks[] = $task;
+ }
+ $this->taskProcessingManager->method('getTasks')->willReturn($tasks);
+
+ $this->assertEquals(SetupResult::SUCCESS, $this->check->run()->getSeverity());
+ }
+
+ public function testFail(): void {
+ $tasks = [];
+ for ($i = 0; $i < 100; $i++) {
+ $task = new Task('test', ['test' => 'test'], 'settings', 'user' . $i);
+ $task->setStartedAt(0);
+ if ($i < 30) {
+ $task->setScheduledAt(60 * 5); // 30% get 5mins
+ } else {
+ $task->setScheduledAt(60); // the rest gets 1min
+ }
+ $tasks[] = $task;
+ }
+ $this->taskProcessingManager->method('getTasks')->willReturn($tasks);
+
+ $this->assertEquals(SetupResult::WARNING, $this->check->run()->getSeverity());
+ }
+}
diff --git a/apps/settings/tests/SetupChecks/WellKnownUrlsTest.php b/apps/settings/tests/SetupChecks/WellKnownUrlsTest.php
new file mode 100644
index 00000000000..d55835d66fc
--- /dev/null
+++ b/apps/settings/tests/SetupChecks/WellKnownUrlsTest.php
@@ -0,0 +1,215 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace OCA\Settings\Tests\SetupChecks;
+
+use OCA\Settings\SetupChecks\WellKnownUrls;
+use OCP\Http\Client\IClientService;
+use OCP\Http\Client\IResponse;
+use OCP\IConfig;
+use OCP\IL10N;
+use OCP\IURLGenerator;
+use OCP\SetupCheck\SetupResult;
+use PHPUnit\Framework\MockObject\MockObject;
+use Psr\Log\LoggerInterface;
+use Test\TestCase;
+
+class WellKnownUrlsTest extends TestCase {
+ private IL10N&MockObject $l10n;
+ private IConfig&MockObject $config;
+ private IURLGenerator&MockObject $urlGenerator;
+ private IClientService&MockObject $clientService;
+ private LoggerInterface&MockObject $logger;
+ private WellKnownUrls&MockObject $setupcheck;
+
+ protected function setUp(): void {
+ parent::setUp();
+
+ /** @var IL10N&MockObject */
+ $this->l10n = $this->createMock(IL10N::class);
+ $this->l10n->expects($this->any())
+ ->method('t')
+ ->willReturnCallback(function ($message, array $replace) {
+ return vsprintf($message, $replace);
+ });
+
+ $this->config = $this->createMock(IConfig::class);
+ $this->urlGenerator = $this->createMock(IURLGenerator::class);
+ $this->clientService = $this->createMock(IClientService::class);
+ $this->logger = $this->createMock(LoggerInterface::class);
+
+ $this->setupcheck = $this->getMockBuilder(WellKnownUrls::class)
+ ->onlyMethods(['runRequest'])
+ ->setConstructorArgs([
+ $this->l10n,
+ $this->config,
+ $this->urlGenerator,
+ $this->clientService,
+ $this->logger,
+ ])
+ ->getMock();
+ }
+
+ /**
+ * Test that the SetupCheck is skipped if the system config is set
+ */
+ public function testDisabled(): void {
+ $this->config
+ ->expects($this->once())
+ ->method('getSystemValueBool')
+ ->with('check_for_working_wellknown_setup')
+ ->willReturn(false);
+
+ $this->setupcheck
+ ->expects($this->never())
+ ->method('runRequest');
+
+ $result = $this->setupcheck->run();
+ $this->assertEquals(SetupResult::INFO, $result->getSeverity());
+ $this->assertMatchesRegularExpression('/check was skipped/', $result->getDescription());
+ }
+
+ /**
+ * Test what happens if the local server could not be reached (no response from the requests)
+ */
+ public function testNoResponse(): void {
+ $this->config
+ ->expects($this->once())
+ ->method('getSystemValueBool')
+ ->with('check_for_working_wellknown_setup')
+ ->willReturn(true);
+
+ $this->setupcheck
+ ->expects($this->once())
+ ->method('runRequest')
+ ->will($this->generate([]));
+
+ $result = $this->setupcheck->run();
+ $this->assertEquals(SetupResult::INFO, $result->getSeverity());
+ $this->assertMatchesRegularExpression('/^Could not check/', $result->getDescription());
+ }
+
+ /**
+ * Test responses
+ */
+ #[\PHPUnit\Framework\Attributes\DataProvider('dataTestResponses')]
+ public function testResponses($responses, string $expectedSeverity): void {
+ $this->config
+ ->expects($this->once())
+ ->method('getSystemValueBool')
+ ->with('check_for_working_wellknown_setup')
+ ->willReturn(true);
+
+ $this->setupcheck
+ ->expects($this->atLeastOnce())
+ ->method('runRequest')
+ ->willReturnOnConsecutiveCalls(...$responses);
+
+ $result = $this->setupcheck->run();
+ $this->assertEquals($expectedSeverity, $result->getSeverity());
+ }
+
+ public function dataTestResponses(): array {
+ $createResponse = function (int $statuscode, array $header = []): IResponse&MockObject {
+ $response = $this->createMock(IResponse::class);
+ $response->expects($this->any())
+ ->method('getStatusCode')
+ ->willReturn($statuscode);
+ $response->expects($this->any())
+ ->method('getHeader')
+ ->willReturnCallback(fn ($name) => $header[$name] ?? '');
+ return $response;
+ };
+
+ $wellKnownHeader = ['X-NEXTCLOUD-WELL-KNOWN' => 'yes'];
+
+ return [
+ 'expected codes' => [
+ [
+ $this->generate([$createResponse(200, $wellKnownHeader)]),
+ $this->generate([$createResponse(200, $wellKnownHeader)]),
+ $this->generate([$createResponse(207)]),
+ $this->generate([$createResponse(207)]),
+ ],
+ SetupResult::SUCCESS,
+ ],
+ 'late response with expected codes' => [
+ [
+ $this->generate([$createResponse(404), $createResponse(200, $wellKnownHeader)]),
+ $this->generate([$createResponse(404), $createResponse(200, $wellKnownHeader)]),
+ $this->generate([$createResponse(404), $createResponse(207)]),
+ $this->generate([$createResponse(404), $createResponse(207)]),
+ ],
+ SetupResult::SUCCESS,
+ ],
+ 'working but disabled webfinger' => [
+ [
+ $this->generate([$createResponse(404, $wellKnownHeader)]),
+ $this->generate([$createResponse(404, $wellKnownHeader)]),
+ $this->generate([$createResponse(207)]),
+ $this->generate([$createResponse(207)]),
+ ],
+ SetupResult::SUCCESS,
+ ],
+ 'unauthorized webdav but with correct configured redirect' => [
+ [
+ $this->generate([$createResponse(404, $wellKnownHeader)]),
+ $this->generate([$createResponse(404, $wellKnownHeader)]),
+ $this->generate([$createResponse(401, ['X-Guzzle-Redirect-History' => 'https://example.com,https://example.com/remote.php/dav/'])]),
+ $this->generate([$createResponse(401, ['X-Guzzle-Redirect-History' => 'https://example.com/remote.php/dav/'])]),
+ ],
+ SetupResult::SUCCESS,
+ ],
+ 'not configured path' => [
+ [
+ $this->generate([$createResponse(404)]),
+ $this->generate([$createResponse(404)]),
+ $this->generate([$createResponse(404)]),
+ $this->generate([$createResponse(404)]),
+ ],
+ SetupResult::WARNING,
+ ],
+ 'Invalid webfinger' => [
+ [
+ $this->generate([$createResponse(404)]),
+ $this->generate([$createResponse(404, $wellKnownHeader)]),
+ $this->generate([$createResponse(207)]),
+ $this->generate([$createResponse(207)]),
+ ],
+ SetupResult::WARNING,
+ ],
+ 'Invalid nodeinfo' => [
+ [
+ $this->generate([$createResponse(404, $wellKnownHeader)]),
+ $this->generate([$createResponse(404)]),
+ $this->generate([$createResponse(207)]),
+ $this->generate([$createResponse(207)]),
+ ],
+ SetupResult::WARNING,
+ ],
+ 'Invalid caldav' => [
+ [
+ $this->generate([$createResponse(404, $wellKnownHeader)]),
+ $this->generate([$createResponse(404, $wellKnownHeader)]),
+ $this->generate([$createResponse(404)]),
+ $this->generate([$createResponse(207)]),
+ ],
+ SetupResult::WARNING,
+ ],
+ ];
+ }
+
+ /**
+ * Helper function creates a nicer interface for mocking Generator behavior
+ */
+ protected function generate(array $yield_values) {
+ return $this->returnCallback(function () use ($yield_values) {
+ yield from $yield_values;
+ });
+ }
+}