Skip to content

Commit f01a20c

Browse files
committed
テスト assertion を完全一致に強化(concat mutation 対策)
サブ B 追加分の expectExceptionMessage(部分一致)が Concat / ConcatOperandRemoval mutation を kill できず、CI Mutation Testing で MSI が 94% に低下していた(閾値 95%)。 サブ A の前例(commit e93c947)と同じ知見に基づき、try-catch + assertSame で完全一致検証に切り替える。 - ConnectionConfigResolverTest: 13 件を try-catch + assertSame に変更 - ConnectionManagerTest (Unit): 5 件を同様に変更 - ApplicationTest: - Phase 3 retrofit 2 件を assertStringStartsWith + previous chain 検証に変更(メッセージに動的部分が含まれるため完全一致不可) - サブ B 8 件を try-catch + assertSame に変更 - ConnectionConfigResolverTest::testResolvePdoOptionsMergesUserOptions: options に複数要素を渡すよう変更(ArrayOneItem mutation 対策) MSI 95% 達成(infection exit 0)。
1 parent fa5402d commit f01a20c

3 files changed

Lines changed: 317 additions & 165 deletions

File tree

tests/Unit/Database/ConnectionConfigResolverTest.php

Lines changed: 176 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -60,27 +60,37 @@ public function testValidateAcceptsValidConfig(array $config): void
6060

6161
public function testValidateRejectsUnknownKey(): void
6262
{
63-
$this->expectException(InvalidConfigException::class);
64-
$this->expectExceptionMessage('Connection [master]: unsupported config key "query_timeout_ms".');
65-
66-
ConnectionConfigResolver::validate('master', [
67-
'driver' => 'mysql',
68-
'host' => 'localhost',
69-
'database' => 'app',
70-
'query_timeout_ms' => 5000,
71-
]);
63+
try {
64+
ConnectionConfigResolver::validate('master', [
65+
'driver' => 'mysql',
66+
'host' => 'localhost',
67+
'database' => 'app',
68+
'query_timeout_ms' => 5000,
69+
]);
70+
$this->fail('Expected InvalidConfigException');
71+
} catch (InvalidConfigException $e) {
72+
$this->assertSame(
73+
'Connection [master]: unsupported config key "query_timeout_ms".',
74+
$e->getMessage(),
75+
);
76+
}
7277
}
7378

7479
public function testValidateRejectsMistypedKey(): void
7580
{
76-
$this->expectException(InvalidConfigException::class);
77-
$this->expectExceptionMessage('Connection [master]: unsupported config key "databse".');
78-
79-
ConnectionConfigResolver::validate('master', [
80-
'driver' => 'mysql',
81-
'host' => 'localhost',
82-
'databse' => 'app',
83-
]);
81+
try {
82+
ConnectionConfigResolver::validate('master', [
83+
'driver' => 'mysql',
84+
'host' => 'localhost',
85+
'databse' => 'app',
86+
]);
87+
$this->fail('Expected InvalidConfigException');
88+
} catch (InvalidConfigException $e) {
89+
$this->assertSame(
90+
'Connection [master]: unsupported config key "databse".',
91+
$e->getMessage(),
92+
);
93+
}
8494
}
8595

8696
/**
@@ -105,60 +115,85 @@ public function testValidateRejectsMissingRequiredKey(string $missingKey): void
105115
];
106116
unset($config[$missingKey]);
107117

108-
$this->expectException(InvalidConfigException::class);
109-
$this->expectExceptionMessage('Connection [master]: missing required config key "' . $missingKey . '".');
110-
111-
ConnectionConfigResolver::validate('master', $config);
118+
try {
119+
ConnectionConfigResolver::validate('master', $config);
120+
$this->fail('Expected InvalidConfigException');
121+
} catch (InvalidConfigException $e) {
122+
$this->assertSame(
123+
'Connection [master]: missing required config key "' . $missingKey . '".',
124+
$e->getMessage(),
125+
);
126+
}
112127
}
113128

114129
public function testValidateRejectsUnsupportedDriver(): void
115130
{
116-
$this->expectException(InvalidConfigException::class);
117-
$this->expectExceptionMessage("Connection [master]: unsupported driver \"pgsql\". Only 'mysql' is supported.");
118-
119-
ConnectionConfigResolver::validate('master', [
120-
'driver' => 'pgsql',
121-
'host' => 'localhost',
122-
'database' => 'app',
123-
]);
131+
try {
132+
ConnectionConfigResolver::validate('master', [
133+
'driver' => 'pgsql',
134+
'host' => 'localhost',
135+
'database' => 'app',
136+
]);
137+
$this->fail('Expected InvalidConfigException');
138+
} catch (InvalidConfigException $e) {
139+
$this->assertSame(
140+
"Connection [master]: unsupported driver \"pgsql\". Only 'mysql' is supported.",
141+
$e->getMessage(),
142+
);
143+
}
124144
}
125145

126146
public function testValidateRejectsNonStringDriver(): void
127147
{
128-
$this->expectException(InvalidConfigException::class);
129-
$this->expectExceptionMessage('Connection [master]: config key "driver" must be a string.');
130-
131-
ConnectionConfigResolver::validate('master', [
132-
'driver' => 1,
133-
'host' => 'localhost',
134-
'database' => 'app',
135-
]);
148+
try {
149+
ConnectionConfigResolver::validate('master', [
150+
'driver' => 1,
151+
'host' => 'localhost',
152+
'database' => 'app',
153+
]);
154+
$this->fail('Expected InvalidConfigException');
155+
} catch (InvalidConfigException $e) {
156+
$this->assertSame(
157+
'Connection [master]: config key "driver" must be a string.',
158+
$e->getMessage(),
159+
);
160+
}
136161
}
137162

138163
public function testValidateRejectsNonIntPort(): void
139164
{
140-
$this->expectException(InvalidConfigException::class);
141-
$this->expectExceptionMessage('Connection [master]: config key "port" must be an integer.');
142-
143-
ConnectionConfigResolver::validate('master', [
144-
'driver' => 'mysql',
145-
'host' => 'localhost',
146-
'database' => 'app',
147-
'port' => '3306',
148-
]);
165+
try {
166+
ConnectionConfigResolver::validate('master', [
167+
'driver' => 'mysql',
168+
'host' => 'localhost',
169+
'database' => 'app',
170+
'port' => '3306',
171+
]);
172+
$this->fail('Expected InvalidConfigException');
173+
} catch (InvalidConfigException $e) {
174+
$this->assertSame(
175+
'Connection [master]: config key "port" must be an integer.',
176+
$e->getMessage(),
177+
);
178+
}
149179
}
150180

151181
public function testValidateRejectsNonStringNullableUsername(): void
152182
{
153-
$this->expectException(InvalidConfigException::class);
154-
$this->expectExceptionMessage('Connection [master]: config key "username" must be a string or null.');
155-
156-
ConnectionConfigResolver::validate('master', [
157-
'driver' => 'mysql',
158-
'host' => 'localhost',
159-
'database' => 'app',
160-
'username' => 123,
161-
]);
183+
try {
184+
ConnectionConfigResolver::validate('master', [
185+
'driver' => 'mysql',
186+
'host' => 'localhost',
187+
'database' => 'app',
188+
'username' => 123,
189+
]);
190+
$this->fail('Expected InvalidConfigException');
191+
} catch (InvalidConfigException $e) {
192+
$this->assertSame(
193+
'Connection [master]: config key "username" must be a string or null.',
194+
$e->getMessage(),
195+
);
196+
}
162197
}
163198

164199
/**
@@ -167,77 +202,113 @@ public function testValidateRejectsNonStringNullableUsername(): void
167202
public static function invalidIdentifierProvider(): array
168203
{
169204
return [
170-
'charset with space' => ['charset', 'utf8 mb4', 'must contain only alphanumeric and underscore characters'],
171-
'charset with semicolon' => ['charset', 'utf8mb4;DROP', 'must contain only alphanumeric and underscore characters'],
172-
'collation with hyphen' => ['collation', 'utf8mb4-unicode', 'must contain only alphanumeric and underscore characters'],
173-
'charset non-string' => ['charset', 123, 'must be a string'],
205+
'charset with space' => [
206+
'charset', 'utf8 mb4',
207+
'Connection [master]: config key "charset" must contain only alphanumeric and underscore characters, got "utf8 mb4".',
208+
],
209+
'charset with semicolon' => [
210+
'charset', 'utf8mb4;DROP',
211+
'Connection [master]: config key "charset" must contain only alphanumeric and underscore characters, got "utf8mb4;DROP".',
212+
],
213+
'collation with hyphen' => [
214+
'collation', 'utf8mb4-unicode',
215+
'Connection [master]: config key "collation" must contain only alphanumeric and underscore characters, got "utf8mb4-unicode".',
216+
],
217+
'charset non-string' => [
218+
'charset', 123,
219+
'Connection [master]: config key "charset" must be a string.',
220+
],
174221
];
175222
}
176223

177224
#[DataProvider('invalidIdentifierProvider')]
178225
public function testValidateRejectsInvalidIdentifier(string $key, mixed $value, string $expectedMessage): void
179226
{
180-
$this->expectException(InvalidConfigException::class);
181-
$this->expectExceptionMessageMatches('/' . preg_quote($expectedMessage, '/') . '/');
182-
183-
ConnectionConfigResolver::validate('master', [
184-
'driver' => 'mysql',
185-
'host' => 'localhost',
186-
'database' => 'app',
187-
$key => $value,
188-
]);
227+
try {
228+
ConnectionConfigResolver::validate('master', [
229+
'driver' => 'mysql',
230+
'host' => 'localhost',
231+
'database' => 'app',
232+
$key => $value,
233+
]);
234+
$this->fail('Expected InvalidConfigException');
235+
} catch (InvalidConfigException $e) {
236+
$this->assertSame($expectedMessage, $e->getMessage());
237+
}
189238
}
190239

191240
public function testValidateRejectsNonArrayOptions(): void
192241
{
193-
$this->expectException(InvalidConfigException::class);
194-
$this->expectExceptionMessage('Connection [master]: config key "options" must be an array.');
195-
196-
ConnectionConfigResolver::validate('master', [
197-
'driver' => 'mysql',
198-
'host' => 'localhost',
199-
'database' => 'app',
200-
'options' => 'invalid',
201-
]);
242+
try {
243+
ConnectionConfigResolver::validate('master', [
244+
'driver' => 'mysql',
245+
'host' => 'localhost',
246+
'database' => 'app',
247+
'options' => 'invalid',
248+
]);
249+
$this->fail('Expected InvalidConfigException');
250+
} catch (InvalidConfigException $e) {
251+
$this->assertSame(
252+
'Connection [master]: config key "options" must be an array.',
253+
$e->getMessage(),
254+
);
255+
}
202256
}
203257

204258
public function testValidateRejectsStringKeyedOptions(): void
205259
{
206-
$this->expectException(InvalidConfigException::class);
207-
$this->expectExceptionMessage('Connection [master]: config key "options" must be an array with integer (PDO::ATTR_*) keys.');
208-
209-
ConnectionConfigResolver::validate('master', [
210-
'driver' => 'mysql',
211-
'host' => 'localhost',
212-
'database' => 'app',
213-
'options' => ['attr_persistent' => false],
214-
]);
260+
try {
261+
ConnectionConfigResolver::validate('master', [
262+
'driver' => 'mysql',
263+
'host' => 'localhost',
264+
'database' => 'app',
265+
'options' => ['attr_persistent' => false],
266+
]);
267+
$this->fail('Expected InvalidConfigException');
268+
} catch (InvalidConfigException $e) {
269+
$this->assertSame(
270+
'Connection [master]: config key "options" must be an array with integer (PDO::ATTR_*) keys.',
271+
$e->getMessage(),
272+
);
273+
}
215274
}
216275

217276
public function testValidateRejectsIntegerKey(): void
218277
{
219278
// Application::filterStringKeys removes int keys before the config
220279
// reaches the resolver, so this scenario only fires when the resolver
221280
// is called directly. Cover the defensive `is_string($key)` branch.
222-
$this->expectException(InvalidConfigException::class);
223-
$this->expectExceptionMessage('Connection [master]: unsupported config key "0".');
224-
225-
ConnectionConfigResolver::validate('master', [
226-
'driver' => 'mysql',
227-
'host' => 'localhost',
228-
'database' => 'app',
229-
0 => 'unexpected_int_key',
230-
]);
281+
try {
282+
ConnectionConfigResolver::validate('master', [
283+
'driver' => 'mysql',
284+
'host' => 'localhost',
285+
'database' => 'app',
286+
0 => 'unexpected_int_key',
287+
]);
288+
$this->fail('Expected InvalidConfigException');
289+
} catch (InvalidConfigException $e) {
290+
$this->assertSame(
291+
'Connection [master]: unsupported config key "0".',
292+
$e->getMessage(),
293+
);
294+
}
231295
}
232296

233297
public function testValidateIncludesConnectionNameInMessage(): void
234298
{
235-
$this->expectException(InvalidConfigException::class);
236-
$this->expectExceptionMessage('Connection [analytics]:');
237-
238-
ConnectionConfigResolver::validate('analytics', [
239-
'driver' => 'mysql',
240-
]);
299+
// Confirms the connection name is propagated into all error messages.
300+
// Asserts the full message so concat-mutation cannot escape.
301+
try {
302+
ConnectionConfigResolver::validate('analytics', [
303+
'driver' => 'mysql',
304+
]);
305+
$this->fail('Expected InvalidConfigException');
306+
} catch (InvalidConfigException $e) {
307+
$this->assertSame(
308+
'Connection [analytics]: missing required config key "host".',
309+
$e->getMessage(),
310+
);
311+
}
241312
}
242313

243314
public function testValidateReturnsValidatedConfigWithValues(): void
@@ -387,18 +458,22 @@ public function testResolvePdoOptionsAppliesCollationWithDefaultCharset(): void
387458

388459
public function testResolvePdoOptionsMergesUserOptions(): void
389460
{
461+
// Multiple user options ensures the resolver returns all entries
462+
// (kills ArrayOneItem mutation that would slice to a single element).
390463
$validated = ConnectionConfigResolver::validate('master', [
391464
'driver' => 'mysql',
392465
'host' => 'localhost',
393466
'database' => 'app',
394467
'options' => [
395468
PDO::ATTR_PERSISTENT => true,
469+
PDO::ATTR_AUTOCOMMIT => false,
396470
],
397471
]);
398472

399473
$options = ConnectionConfigResolver::resolvePdoOptions($validated);
400474

401475
$this->assertSame(true, $options[PDO::ATTR_PERSISTENT]);
476+
$this->assertSame(false, $options[PDO::ATTR_AUTOCOMMIT]);
402477
$this->assertSame(2, $options[PDO::ATTR_TIMEOUT]);
403478
$this->assertSame('SET NAMES utf8mb4', $options[PdoMysql::ATTR_INIT_COMMAND]);
404479
}

0 commit comments

Comments
 (0)