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

Failed Conditions
Pull Request — master (#1038)
by Dan
04:32
created

DatabaseIntegrationTest   A

Complexity

Total Complexity 28

Size/Duplication

Total Lines 260
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 123
dl 0
loc 260
rs 10
c 0
b 0
f 0
wmc 28

26 Methods

Rating   Name   Duplication   Size   Complexity  
A test_closing_database_returns_boolean() 0 6 1
A setUp() 0 4 1
A test_escapeArray() 0 11 1
A test_getInstance_always_returns_new_instance() 0 6 1
A test_getInstance_will_not_perform_reconnect_if_connection_not_closed() 0 9 1
A test_performing_operations_on_closed_database_throws_error() 0 9 1
A test_getInstance_will_perform_reconnect_after_connection_closed() 0 13 1
A test_nextRecord_no_result() 0 5 1
A test_lockTable_throws_if_read_other_table() 0 11 2
A test_hasField() 0 7 1
A test_getBoolean_with_non_boolean_field() 0 7 1
A test_nextRecord_no_resource() 0 6 1
A test_escapeNumber_nonnumeric_throws() 0 5 1
A test_escapeString() 0 10 1
A test_escapeArray_nested_array_throws() 0 6 1
A test_escapeString_null_throws() 0 4 1
A test_escapeNumber() 0 9 1
A test_escapeMicrotime() 0 6 1
A test_inversion_of_escape_and_get() 0 22 2
A test__construct_happy_path() 0 3 1
A test_escapeObject() 0 10 1
A test_requireRecord_too_many_rows() 0 7 1
A test_requireRecord() 0 6 1
A test_mysqli_factory() 0 7 1
A test_lockTable_allows_read() 0 12 1
A test_escapeBoolean() 0 5 1
1
<?php declare(strict_types=1);
2
3
namespace SmrTest\lib\DefaultGame;
4
5
use mysqli;
6
use PHPUnit\Framework\TestCase;
7
use Smr\Container\DiContainer;
8
use Smr\Database;
9
use Smr\DatabaseProperties;
10
11
/**
12
 * This is an integration test, but does not need to extend BaseIntegrationTest since we are not writing any data.
13
 * @covers \Smr\Database
14
 */
15
class DatabaseIntegrationTest 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_mysqli_factory() {
24
		// Given database properties are retrieved from the container
25
		$dbProperties = DiContainer::get(DatabaseProperties::class);
26
		// When using the factory to retrieve a mysqli instance
27
		$mysql = Database::mysqliFactory($dbProperties);
28
		// Then the connection is successful
29
		self::assertNotNull($mysql->server_info);
30
	}
31
32
	public function test__construct_happy_path() {
33
		$db = DiContainer::get(Database::class);
34
		$this->assertNotNull($db);
35
	}
36
37
	public function test_getInstance_always_returns_new_instance() {
38
		// Given a Database object
39
		$original = Database::getInstance();
40
		// When calling getInstance again
41
		$second = Database::getInstance();
42
		self::assertNotSame($original, $second);
43
	}
44
45
	public function test_performing_operations_on_closed_database_throws_error() {
46
		// Given a Database instance
47
		$db = Database::getInstance();
48
		// And disconnect is called
49
		$db->close();
50
		// When calling database methods
51
		$this->expectException(\Error::class);
52
		$this->expectExceptionMessage('Typed property Smr\Database::$dbConn must not be accessed before initialization');
53
		$db->query("foo query");
54
	}
55
56
	public function test_closing_database_returns_boolean() {
57
		$db = Database::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 Database instance
68
		$db = Database::getInstance();
69
		// And disconnect is called
70
		$db->close();
71
		// And Database is retrieved from the container
72
		$db = Database::getInstance();
73
		// When performing a query
74
		$db ->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 Database instance
83
		Database::getInstance();
84
		// And get instance is called again
85
		Database::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 = Database::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 Smr\Database::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 = Database::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 = Database::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 = Database::getInstance();
119
		$this->expectException(\TypeError::class);
120
		$db->escapeString(null);
121
	}
122
123
	public function test_escapeArray() {
124
		$db = Database::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 = Database::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 = Database::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 = Database::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 = Database::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 = Database::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 = Database::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 = Database::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 = Database::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 = Database::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 = Database::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 = Database::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 = Database::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 = Database::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