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 (#1094)
by Dan
06:37
created

DatabaseIntegrationTest::test_nextRecord_no_result()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 0
dl 0
loc 5
rs 10
c 0
b 0
f 0
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