Scrutinizer GitHub App not installed

We could not synchronize checks via GitHub's checks API since Scrutinizer's GitHub App is not installed for this repository.

Install GitHub App

Passed
Push — master ( 299ad6...f555d0 )
by Dan
04:49
created

test_closing_database_returns_boolean()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
cc 1
eloc 3
nc 1
nop 0
dl 0
loc 6
rs 10
c 1
b 1
f 0
1
<?php declare(strict_types=1);
2
3
namespace SmrTest\lib\DefaultGame;
4
5
use MySqlDatabase;
6
use mysqli;
7
use PHPUnit\Framework\TestCase;
8
use Smr\Container\DiContainer;
9
use Smr\MySqlProperties;
10
11
/**
12
 * This is an integration test, but does not need to extend BaseIntegrationTest since we are not writing any data.
13
 * @covers MySqlDatabase
14
 */
15
class MySqlDatabaseIntegrationTest extends TestCase {
16
17
	protected function setUp(): void {
18
		// Start each test with a fresh container (and mysqli connection).
19
		// This ensures the independence of each test.
20
		DiContainer::initializeContainer();
21
	}
22
23
	public function test_mysql_factory() {
24
		// Given mysql properties are retrieved from the container
25
		$mysqlProperties = DiContainer::get(MySqlProperties::class);
26
		// When using the factory to retrieve a mysqli instance
27
		$mysqlDatabase = MySqlDatabase::mysqliFactory($mysqlProperties);
28
		// Then the connection is successful
29
		self::assertNotNull($mysqlDatabase->server_info);
30
	}
31
32
	public function test__construct_happy_path() {
33
		$mysqlDatabase = DiContainer::get(MySqlDatabase::class);
34
		$this->assertNotNull($mysqlDatabase);
35
	}
36
37
	public function test_getInstance_always_returns_new_instance() {
38
		// Given a MySqlDatabase object
39
		$original = MySqlDatabase::getInstance();
40
		// When calling getInstance again
41
		$second = MySqlDatabase::getInstance();
42
		self::assertNotSame($original, $second);
43
	}
44
45
	public function test_performing_operations_on_closed_database_throws_error() {
46
		// Given a mysql database instance
47
		$mysqlDatabase = MySqlDatabase::getInstance();
48
		// And disconnect is called
49
		$mysqlDatabase->close();
50
		// When calling database methods
51
		$this->expectException(\Error::class);
52
		$this->expectExceptionMessage('Typed property MySqlDatabase::$dbConn must not be accessed before initialization');
53
		$mysqlDatabase->query("foo query");
54
	}
55
56
	public function test_closing_database_returns_boolean() {
57
		$db = MySqlDatabase::getInstance();
58
		// Returns true when closing an open database connection
59
		self::assertTrue($db->close());
60
		// Returns false if the database has already been closed
61
		self::assertFalse($db->close());
62
	}
63
64
	public function test_getInstance_will_perform_reconnect_after_connection_closed() {
65
		// Given an original mysql connection
66
		$originalMysql = DiContainer::get(mysqli::class);
67
		// And a mysql database instance
68
		$mysqlDatabase = MySqlDatabase::getInstance();
69
		// And disconnect is called
70
		$mysqlDatabase->close();
71
		// And mysql database is retrieved from the container
72
		$mysqlDatabase = MySqlDatabase::getInstance();
73
		// When performing a query
74
		$mysqlDatabase->query("select 1");
75
		// Then new mysqli instance is not the same as the initial mock
76
		self::assertNotSame($originalMysql, DiContainer::get(mysqli::class));
77
	}
78
79
	public function test_getInstance_will_not_perform_reconnect_if_connection_not_closed() {
80
		// Given an original mysql connection
81
		$originalMysql = DiContainer::get(mysqli::class);
82
		// And a mysql database instance
83
		MySqlDatabase::getInstance();
84
		// And get instance is called again
85
		MySqlDatabase::getInstance();
86
		// Then the two mysqli instances are the same
87
		self::assertSame($originalMysql, DiContainer::get(mysqli::class));
88
	}
89
90
	public function test_escapeMicrotime() {
91
		$db = MySqlDatabase::getInstance();
92
		// The current microtime must not throw an exception
93
		$db->escapeMicrotime(microtime(true));
0 ignored issues
show
Bug introduced by
It seems like microtime(true) can also be of type string; however, parameter $microtime of MySqlDatabase::escapeMicrotime() does only seem to accept double, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

93
		$db->escapeMicrotime(/** @scrutinizer ignore-type */ microtime(true));
Loading history...
94
		// Check that the formatting preserves all digits
95
		self::assertSame("1608455259123456", $db->escapeMicrotime(1608455259.123456));
96
	}
97
98
	public function test_escapeBoolean() {
99
		$db = MySqlDatabase::getInstance();
100
		// Test both boolean values
101
		self::assertSame("'TRUE'", $db->escapeBoolean(true));
102
		self::assertSame("'FALSE'", $db->escapeBoolean(false));
103
	}
104
105
	public function test_escapeString() {
106
		$db = MySqlDatabase::getInstance();
107
		// Test the empty string
108
		self::assertSame("''", $db->escapeString(''));
109
		self::assertSame('NULL', $db->escapeString('', true)); // nullable
110
		// Test null
111
		self::assertSame('NULL', $db->escapeString(null, true)); // nullable
112
		// Test a normal string
113
		self::assertSame("'bla'", $db->escapeString('bla'));
114
		self::assertSame("'bla'", $db->escapeString('bla', true)); // nullable
115
	}
116
117
	public function test_escapeString_null_throws() {
118
		$db = MySqlDatabase::getInstance();
119
		$this->expectException(\TypeError::class);
120
		$db->escapeString(null);
121
	}
122
123
	public function test_escapeArray() {
124
		$db = MySqlDatabase::getInstance();
125
		// Test a mixed array
126
		self::assertSame("'a',2,'c'", $db->escapeArray(['a', 2, 'c']));
127
		// Test a different implodeString
128
		self::assertSame("'a':2:'c'", $db->escapeArray(['a', 2, 'c'], ':'));
129
		// Test escapeIndividually=false
130
		self::assertSame("'a,2,c'", $db->escapeArray(['a', 2, 'c'], ',', false));
131
		// Test nested arrays
132
		// Warning: The array is flattened, which may be unexpected!
133
		self::assertSame("'a','x',9,2", $db->escapeArray(['a', ['x', 9], 2], ',', true));
134
	}
135
136
	public function test_escapeArray_nested_array_throws() {
137
		// Warning: It is dangerous to use nested arrays with escapeIndividually=false
138
		$db = MySqlDatabase::getInstance();
139
		$this->expectWarning();
140
		$this->expectWarningMessage('Array to string conversion');
141
		$db->escapeArray(['a', ['x', 9, 'y'], 2, 'c'], ':', false);
142
	}
143
144
	public function test_escapeNumber() {
145
		// No escaping is done of numeric types
146
		$db = MySqlDatabase::getInstance();
147
		// Test int
148
		self::assertSame(42, $db->escapeNumber(42));
149
		// Test float
150
		self::assertSame(0.21, $db->escapeNumber(0.21));
151
		// Test numeric string
152
		self::assertSame('42', $db->escapeNumber('42'));
153
	}
154
155
	public function test_escapeNumber_nonnumeric_throws() {
156
		$db = MySqlDatabase::getInstance();
157
		$this->expectException(\RuntimeException::class);
158
		$this->expectExceptionMessage('Not a number');
159
		$db->escapeNumber('bla');
160
	}
161
162
	public function test_escapeObject() {
163
		$db = MySqlDatabase::getInstance();
164
		// Test null
165
		self::assertSame('NULL', $db->escapeObject(null, false, true));
166
		// Test empty array
167
		self::assertSame("'a:0:{}'", $db->escapeObject([]));
168
		// Test empty string
169
		self::assertSame('\'s:0:\"\";\'', $db->escapeObject(''));
170
		// Test zero
171
		self::assertSame("'i:0;'", $db->escapeObject(0));
172
	}
173
174
	public function test_lockTable_throws_if_read_other_table() {
175
		$db = MySqlDatabase::getInstance();
176
		$db->lockTable('player');
177
		$this->expectException(\RuntimeException::class);
178
		$this->expectExceptionMessage("Table 'account' was not locked with LOCK TABLES");
179
		try {
180
			$db->query('SELECT 1 FROM account LIMIT 1');
181
		} catch (\RuntimeException $err) {
182
			// Avoid leaving database in a locked state
183
			$db->unlock();
184
			throw $err;
185
		}
186
	}
187
188
	public function test_lockTable_allows_read() {
189
		$db = MySqlDatabase::getInstance();
190
		$db->lockTable('ship_class');
191
192
		// Perform a query on the locked table
193
		$db->query('SELECT ship_class_name FROM ship_class WHERE ship_class_id = 1');
194
		$db->requireRecord();
195
		self::assertSame(['ship_class_name' => 'Hunter'], $db->getRow());
196
197
		// After unlock we can access other tables again
198
		$db->unlock();
199
		$db->query('SELECT 1 FROM account LIMIT 1');
200
	}
201
202
	public function test_requireRecord() {
203
		$db = MySqlDatabase::getInstance();
204
		// Create a query that returns one record
205
		$db->query('SELECT 1');
206
		$db->requireRecord();
207
		self::assertSame([1 => '1'], $db->getRow());
208
	}
209
210
	public function test_requireRecord_too_many_rows() {
211
		$db = MySqlDatabase::getInstance();
212
		// Create a query that returns two rows
213
		$db->query('SELECT 1 UNION SELECT 2');
214
		$this->expectException(\RuntimeException::class);
215
		$this->expectExceptionMessage('One record required, but found 2');
216
		$db->requireRecord();
217
	}
218
219
	public function test_nextRecord_no_resource() {
220
		$db = MySqlDatabase::getInstance();
221
		$this->expectException(\RuntimeException::class);
222
		$this->expectExceptionMessage('No resource to get record from.');
223
		// Call nextRecord before any query is made
224
		$db->nextRecord();
225
	}
226
227
	public function test_nextRecord_no_result() {
228
		$db = MySqlDatabase::getInstance();
229
		// Construct a query that has an empty result set
230
		$db->query('SELECT 1 FROM ship_class WHERE ship_class_id = 0');
231
		self::assertFalse($db->nextRecord());
232
	}
233
234
	public function test_hasField() {
235
		$db = MySqlDatabase::getInstance();
236
		// Construct a query that has the field 'val', but not 'bla'
237
		$db->query('SELECT 1 AS val');
238
		$db->requireRecord();
239
		self::assertTrue($db->hasField('val'));
240
		self::assertFalse($db->hasField('bla'));
241
	}
242
243
	public function test_inversion_of_escape_and_get() {
244
		$db = MySqlDatabase::getInstance();
245
		// [value, escape function, getter, comparator, extra args]
246
		$params = [
247
			[true, 'escapeBoolean', 'getBoolean', 'assertSame', []],
248
			[false, 'escapeBoolean', 'getBoolean', 'assertSame', []],
249
			[3, 'escapeNumber', 'getInt', 'assertSame', []],
250
			[3.14, 'escapeNumber', 'getFloat', 'assertSame', []],
251
			['hello', 'escapeString', 'getField', 'assertSame', []],
252
			// Test nullable objects
253
			[null, 'escapeObject', 'getObject', 'assertSame', [false, true]],
254
			// Test object with compression
255
			[[1, 2, 3], 'escapeObject', 'getObject', 'assertSame', [true]],
256
			// Test object without compression
257
			[[1, 2, 3], 'escapeObject', 'getObject', 'assertSame', []],
258
			// Microtime takes a float and returns a string because of DateTime::createFromFormat
259
			[microtime(true), 'escapeMicrotime', 'getMicrotime', 'assertEquals', []],
260
		];
261
		foreach ($params as list($value, $escaper, $getter, $cmp, $args)) {
262
			$db->query('SELECT ' . $db->$escaper($value, ...$args) . ' AS val');
263
			$db->requireRecord();
264
			self::$cmp($value, $db->$getter('val', ...$args));
265
		}
266
	}
267
268
	public function test_getBoolean_with_non_boolean_field() {
269
		$db = MySqlDatabase::getInstance();
270
		$db->query('SELECT \'bla\'');
271
		$db->requireRecord();
272
		$this->expectException(\RuntimeException::class);
273
		$this->expectExceptionMessage('Field is not a boolean: bla');
274
		$db->getBoolean('bla');
275
	}
276
277
}
278