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
Push — master ( f174b5...646f17 )
by Dan
21s queued 18s
created

test_reconnect_after_connection_closed()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 12
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 6
c 0
b 0
f 0
nc 1
nop 0
dl 0
loc 12
rs 10
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_same_instance() {
38
		// Given a Database object
39
		$original = Database::getInstance();
40
		// When calling getInstance again
41
		$second = Database::getInstance();
42
		self::assertSame($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->read("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_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 usable again after reconnecting
72
		$db->reconnect();
73
		$db->read("SELECT 1");
74
		// Then new mysqli instance is not the same as the initial mock
75
		self::assertNotSame($originalMysql, DiContainer::get(mysqli::class));
76
	}
77
78
	public function test_reconnect_when_connection_not_closed() {
79
		// Given an original mysql connection
80
		$originalMysql = DiContainer::get(mysqli::class);
81
		// And a Database instance
82
		$db = Database::getInstance();
83
		// And reconnect is called before closing the connection
84
		$db->reconnect();
85
		// Then the two mysqli instances are the same
86
		self::assertSame($originalMysql, DiContainer::get(mysqli::class));
87
	}
88
89
	public function test_escapeMicrotime() {
90
		$db = Database::getInstance();
91
		// The current microtime must not throw an exception
92
		$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

92
		$db->escapeMicrotime(/** @scrutinizer ignore-type */ microtime(true));
Loading history...
93
		// Check that the formatting preserves all digits
94
		self::assertSame("1608455259123456", $db->escapeMicrotime(1608455259.123456));
95
	}
96
97
	public function test_escapeBoolean() {
98
		$db = Database::getInstance();
99
		// Test both boolean values
100
		self::assertSame("'TRUE'", $db->escapeBoolean(true));
101
		self::assertSame("'FALSE'", $db->escapeBoolean(false));
102
	}
103
104
	public function test_escapeString() {
105
		$db = Database::getInstance();
106
		// Test the empty string
107
		self::assertSame("''", $db->escapeString(''));
108
		self::assertSame('NULL', $db->escapeString('', true)); // nullable
109
		// Test null
110
		self::assertSame('NULL', $db->escapeString(null, true)); // nullable
111
		// Test a normal string
112
		self::assertSame("'bla'", $db->escapeString('bla'));
113
		self::assertSame("'bla'", $db->escapeString('bla', true)); // nullable
114
	}
115
116
	public function test_escapeString_null_throws() {
117
		$db = Database::getInstance();
118
		$this->expectException(\TypeError::class);
119
		$db->escapeString(null);
120
	}
121
122
	public function test_escapeArray() {
123
		$db = Database::getInstance();
124
		// Test a mixed array
125
		self::assertSame("'a',2,'c'", $db->escapeArray(['a', 2, 'c']));
126
		// Test a different implodeString
127
		self::assertSame("'a':2:'c'", $db->escapeArray(['a', 2, 'c'], ':'));
128
		// Test escapeIndividually=false
129
		self::assertSame("'a,2,c'", $db->escapeArray(['a', 2, 'c'], ',', false));
130
		// Test nested arrays
131
		// Warning: The array is flattened, which may be unexpected!
132
		self::assertSame("'a','x',9,2", $db->escapeArray(['a', ['x', 9], 2], ',', true));
133
	}
134
135
	public function test_escapeArray_nested_array_throws() {
136
		// Warning: It is dangerous to use nested arrays with escapeIndividually=false
137
		$db = Database::getInstance();
138
		$this->expectWarning();
139
		$this->expectWarningMessage('Array to string conversion');
140
		$db->escapeArray(['a', ['x', 9, 'y'], 2, 'c'], ':', false);
141
	}
142
143
	public function test_escapeNumber() {
144
		// No escaping is done of numeric types
145
		$db = Database::getInstance();
146
		// Test int
147
		self::assertSame(42, $db->escapeNumber(42));
148
		// Test float
149
		self::assertSame(0.21, $db->escapeNumber(0.21));
150
		// Test numeric string
151
		self::assertSame('42', $db->escapeNumber('42'));
152
	}
153
154
	public function test_escapeNumber_nonnumeric_throws() {
155
		$db = Database::getInstance();
156
		$this->expectException(\RuntimeException::class);
157
		$this->expectExceptionMessage('Not a number');
158
		$db->escapeNumber('bla');
159
	}
160
161
	public function test_escapeObject() {
162
		$db = Database::getInstance();
163
		// Test null
164
		self::assertSame('NULL', $db->escapeObject(null, false, true));
165
		// Test empty array
166
		self::assertSame("'a:0:{}'", $db->escapeObject([]));
167
		// Test empty string
168
		self::assertSame('\'s:0:\"\";\'', $db->escapeObject(''));
169
		// Test zero
170
		self::assertSame("'i:0;'", $db->escapeObject(0));
171
	}
172
173
	public function test_lockTable_throws_if_read_other_table() {
174
		$db = Database::getInstance();
175
		$db->lockTable('player');
176
		$this->expectException(\RuntimeException::class);
177
		$this->expectExceptionMessage("Table 'account' was not locked with LOCK TABLES");
178
		try {
179
			$db->read('SELECT 1 FROM account LIMIT 1');
180
		} catch (\RuntimeException $err) {
181
			// Avoid leaving database in a locked state
182
			$db->unlock();
183
			throw $err;
184
		}
185
	}
186
187
	public function test_lockTable_allows_read() {
188
		$db = Database::getInstance();
189
		$db->lockTable('good');
190
191
		// Perform a query on the locked table
192
		$result = $db->read('SELECT good_name FROM good WHERE good_id = 1');
193
		self::assertSame(['good_name' => 'Wood'], $result->record()->getRow());
194
195
		// After unlock we can access other tables again
196
		$db->unlock();
197
		$db->read('SELECT 1 FROM account LIMIT 1');
198
	}
199
200
	public function test_inversion_of_escape_and_get() {
201
		$db = Database::getInstance();
202
		// [value, escape function, getter, comparator, extra args]
203
		$params = [
204
			[true, 'escapeBoolean', 'getBoolean', 'assertSame', []],
205
			[false, 'escapeBoolean', 'getBoolean', 'assertSame', []],
206
			[3, 'escapeNumber', 'getInt', 'assertSame', []],
207
			[3.14, 'escapeNumber', 'getFloat', 'assertSame', []],
208
			['hello', 'escapeString', 'getString', 'assertSame', []],
209
			['hello', 'escapeString', 'getField', 'assertSame', []],
210
			// Test nullable objects
211
			[null, 'escapeString', 'getField', 'assertSame', [true]],
212
			[null, 'escapeObject', 'getObject', 'assertSame', [false, true]],
213
			// Test object with compression
214
			[[1, 2, 3], 'escapeObject', 'getObject', 'assertSame', [true]],
215
			// Test object without compression
216
			[[1, 2, 3], 'escapeObject', 'getObject', 'assertSame', []],
217
			// Microtime takes a float and returns a string because of DateTime::createFromFormat
218
			[microtime(true), 'escapeMicrotime', 'getMicrotime', 'assertEquals', []],
219
		];
220
		foreach ($params as list($value, $escaper, $getter, $cmp, $args)) {
221
			$result = $db->read('SELECT ' . $db->$escaper($value, ...$args) . ' AS val');
222
			self::$cmp($value, $result->record()->$getter('val', ...$args));
223
		}
224
	}
225
226
}
227