1818use Test \TestCase ;
1919
2020class AbstractDatabaseTest extends TestCase {
21+ /**
22+ * Numeric literal instead of PDO::MYSQL_ATTR_SSL_CA: the constant is deprecated
23+ * since PHP 8.5 and only defined when the MySQL driver is available.
24+ */
25+ private const MYSQL_ATTR_SSL_CA = 1008 ;
26+
2127 private SystemConfig &MockObject $ config ;
2228 private ConnectionFactory &MockObject $ connectionFactory ;
2329 private Connection &MockObject $ connection ;
30+ private LoggerInterface &MockObject $ logger ;
2431 private TestDatabase $ database ;
2532
2633 #[\Override]
@@ -30,11 +37,16 @@ protected function setUp(): void {
3037 $ this ->config = $ this ->createMock (SystemConfig::class);
3138 $ this ->connectionFactory = $ this ->createMock (ConnectionFactory::class);
3239 $ this ->connection = $ this ->createMock (Connection::class);
40+ $ this ->logger = $ this ->createMock (LoggerInterface::class);
41+
42+ $ l10n = $ this ->createMock (IL10N ::class);
43+ $ l10n ->method ('t ' )
44+ ->willReturnCallback (fn (string $ text , array $ parameters = []) => vsprintf ($ text , $ parameters ));
3345
3446 $ this ->database = new TestDatabase (
35- $ this -> createMock ( IL10N ::class) ,
47+ $ l10n ,
3648 $ this ->config ,
37- $ this ->createMock (LoggerInterface::class) ,
49+ $ this ->logger ,
3850 $ this ->createMock (ISecureRandom::class),
3951 );
4052 $ this ->database ->connectionFactory = $ this ->connectionFactory ;
@@ -75,6 +87,100 @@ public function testInitializeFallsBackToLocalhost(): void {
7587 ]);
7688 }
7789
90+ /**
91+ * The connection encryption options are only read from the system config, so they have
92+ * to be persisted by initialize() - before any connection is opened by setupDatabase().
93+ */
94+ public function testInitializePersistsDriverOptions (): void {
95+ $ this ->config ->expects ($ this ->once ())
96+ ->method ('setValues ' )
97+ ->with ([
98+ 'dbname ' => 'nextcloud ' ,
99+ 'dbhost ' => 'db.example.org ' ,
100+ 'dbtableprefix ' => 'oc_ ' ,
101+ 'dbdriveroptions ' => [self ::MYSQL_ATTR_SSL_CA => '/ca.pem ' ],
102+ ]);
103+
104+ $ this ->database ->initialize ($ this ->options ([
105+ 'dbdriveroptions ' => [self ::MYSQL_ATTR_SSL_CA => '/ca.pem ' ],
106+ ]));
107+ }
108+
109+ /**
110+ * Only the options of the database being set up may be persisted, every database
111+ * configures an encrypted connection differently.
112+ */
113+ public function testInitializeSkipsOptionsOfOtherDatabases (): void {
114+ $ this ->config ->expects ($ this ->once ())
115+ ->method ('setValues ' )
116+ ->with ([
117+ 'dbname ' => 'nextcloud ' ,
118+ 'dbhost ' => 'db.example.org ' ,
119+ 'dbtableprefix ' => 'oc_ ' ,
120+ ]);
121+
122+ $ this ->database ->initialize ($ this ->options ([
123+ 'pgsql_ssl ' => ['mode ' => 'verify-full ' ],
124+ ]));
125+ }
126+
127+ public static function emptyEncryptionOptions (): array {
128+ return [
129+ 'not provided ' => [[]],
130+ 'empty array ' => [['dbdriveroptions ' => []]],
131+ 'null ' => [['dbdriveroptions ' => null ]],
132+ ];
133+ }
134+
135+ #[\PHPUnit \Framework \Attributes \DataProvider('emptyEncryptionOptions ' )]
136+ public function testInitializeSkipsEmptyEncryptionOptions (array $ additional ): void {
137+ $ this ->config ->expects ($ this ->once ())
138+ ->method ('setValues ' )
139+ ->with ([
140+ 'dbname ' => 'nextcloud ' ,
141+ 'dbhost ' => 'db.example.org ' ,
142+ 'dbtableprefix ' => 'oc_ ' ,
143+ ]);
144+
145+ $ this ->database ->initialize ($ this ->options ($ additional ));
146+ }
147+
148+ /**
149+ * A malformed option must never be persisted, as that would end up configuring an
150+ * unencrypted connection while the admin expects an encrypted one.
151+ */
152+ public function testInitializeRejectsMalformedEncryptionOptions (): void {
153+ $ this ->config ->expects ($ this ->once ())
154+ ->method ('setValues ' )
155+ ->with ([
156+ 'dbname ' => 'nextcloud ' ,
157+ 'dbhost ' => 'db.example.org ' ,
158+ 'dbtableprefix ' => 'oc_ ' ,
159+ ]);
160+ $ this ->logger ->expects ($ this ->once ())
161+ ->method ('error ' );
162+
163+ $ this ->database ->initialize ($ this ->options (['dbdriveroptions ' => '/ca.pem ' ]));
164+ }
165+
166+ public function testValidateRejectsMalformedEncryptionOptions (): void {
167+ $ errors = $ this ->database ->validate ($ this ->options (['dbdriveroptions ' => '/ca.pem ' ]));
168+
169+ $ this ->assertEquals ([
170+ 'The database option "dbdriveroptions" for Test has to be a list of values ' ,
171+ ], $ errors );
172+ }
173+
174+ public function testValidateAcceptsEncryptionOptions (): void {
175+ $ errors = $ this ->database ->validate ($ this ->options ([
176+ 'dbdriveroptions ' => [self ::MYSQL_ATTR_SSL_CA => '/ca.pem ' ],
177+ // not an option of this database, so it is not validated either
178+ 'pgsql_ssl ' => 'verify-full ' ,
179+ ]));
180+
181+ $ this ->assertEquals ([], $ errors );
182+ }
183+
78184 /**
79185 * Host, database name and table prefix must not be passed as additional parameters:
80186 * they are resolved from the system config by the connection factory, so that setup
0 commit comments