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 — main (#1487)
by Dan
06:01
created

AbstractSmrPortTest   A

Complexity

Total Complexity 18

Size/Duplication

Total Lines 369
Duplicated Lines 0 %

Importance

Changes 4
Bugs 0 Features 0
Metric Value
eloc 197
dl 0
loc 369
rs 10
c 4
b 0
f 0
wmc 18

17 Methods

Rating   Name   Duplication   Size   Complexity  
A tearDown() 0 3 1
A test_takeDamage() 0 9 1
A test_addPortGood() 0 9 1
A test_port_primary_keys() 0 7 1
A tearDownAfterClass() 0 3 1
A test_shields() 0 14 1
A test_port_loses_level_on_raid_killshot() 0 34 1
A test_cds() 0 14 1
A test_removePortGood() 0 11 2
A provider_getGoodTransaction() 0 2 1
A test_armour() 0 14 1
A test_setRaceID() 0 7 1
A test_new_port_does_not_exist_yet() 0 3 1
B dataProvider_takeDamage() 0 153 1
A provider_removePortGood() 0 10 1
A test_getGoodTransaction() 0 4 1
A test_getGoodTransaction_throws_if_port_does_not_have_good() 0 6 1
1
<?php declare(strict_types=1);
2
3
namespace SmrTest\lib\DefaultGame;
4
5
use AbstractSmrPort;
6
use Exception;
7
use PHPUnit\Framework\TestCase;
8
use Smr\BountyType;
9
use Smr\Container\DiContainer;
10
use Smr\Database;
11
use Smr\TransactionType;
12
use SmrGame;
13
use SmrPlayer;
14
use SmrSector;
15
16
/**
17
 * @covers AbstractSmrPort
18
 */
19
class AbstractSmrPortTest extends TestCase {
20
21
	protected function tearDown(): void {
22
		AbstractSmrPort::clearCache();
23
		DiContainer::initialize(false);
24
	}
25
26
	public static function tearDownAfterClass(): void {
27
		SmrSector::clearCache();
28
		SmrGame::clearCache();
29
	}
30
31
	public function test_new_port_does_not_exist_yet(): void {
32
		$port = AbstractSmrPort::createPort(1, 1);
33
		self::assertFalse($port->exists());
34
	}
35
36
	public function test_port_primary_keys(): void {
37
		// Ports are keyed on game ID and sector ID
38
		$gameID = 2;
39
		$sectorID = 3;
40
		$port = AbstractSmrPort::createPort($gameID, $sectorID);
41
		self::assertSame($gameID, $port->getGameID());
42
		self::assertSame($sectorID, $port->getSectorID());
43
	}
44
45
	public function test_setRaceID(): void {
46
		$port = AbstractSmrPort::createPort(1, 1);
47
		// New ports start as Neutral
48
		self::assertSame(RACE_NEUTRAL, $port->getRaceID());
49
		// Then check changing the race
50
		$port->setRaceID(RACE_HUMAN);
51
		self::assertSame(RACE_HUMAN, $port->getRaceID());
52
	}
53
54
	public function test_addPortGood(): void {
55
		// When we add the good
56
		$port = AbstractSmrPort::createPort(1, 1);
57
		$port->addPortGood(GOODS_ORE, TransactionType::Buy);
58
		// Insert smaller ID port good second to test sorting
59
		$port->addPortGood(GOODS_WOOD, TransactionType::Sell);
60
		self::assertSame([GOODS_WOOD, GOODS_ORE], $port->getAllGoodIDs());
61
		self::assertSame([GOODS_WOOD], $port->getSellGoodIDs());
62
		self::assertSame([GOODS_ORE], $port->getBuyGoodIDs());
63
	}
64
65
	/**
66
	 * @dataProvider provider_removePortGood
67
	 *
68
	 * @param array<int> $removeGoodIDs
69
	 * @param array<int> $sellRemain
70
	 * @param array<int> $buyRemain
71
	 */
72
	public function test_removePortGood(array $removeGoodIDs, array $sellRemain, array $buyRemain): void {
73
		// Set up a port with a couple goods
74
		$port = AbstractSmrPort::createPort(1, 1);
75
		$port->addPortGood(GOODS_WOOD, TransactionType::Sell);
76
		$port->addPortGood(GOODS_ORE, TransactionType::Buy);
77
		foreach ($removeGoodIDs as $goodID) {
78
			$port->removePortGood($goodID);
79
		}
80
		self::assertSame($sellRemain, $port->getSellGoodIDs());
81
		self::assertSame($buyRemain, $port->getBuyGoodIDs());
82
		self::assertSame(array_merge($sellRemain, $buyRemain), $port->getAllGoodIDs());
83
	}
84
85
	/**
86
	 * @return array<array<array<int>>>
87
	 */
88
	public function provider_removePortGood(): array {
89
		return [
90
			// Remove a good that the port doesn't have
91
			[[GOODS_CIRCUITRY], [GOODS_WOOD], [GOODS_ORE]],
92
			// Remove a buyable good
93
			[[GOODS_WOOD], [], [GOODS_ORE]],
94
			// Remove a sellable good
95
			[[GOODS_ORE], [GOODS_WOOD], []],
96
			// Remove both goods
97
			[[GOODS_WOOD, GOODS_ORE], [], []],
98
		];
99
	}
100
101
	/**
102
	 * @dataProvider provider_getGoodTransaction
103
	 */
104
	public function test_getGoodTransaction(TransactionType $transaction): void {
105
		$port = AbstractSmrPort::createPort(1, 1);
106
		$port->addPortGood(GOODS_ORE, $transaction);
107
		self::assertSame($transaction, $port->getGoodTransaction(GOODS_ORE));
108
	}
109
110
	/**
111
	 * @return array<array<TransactionType>>
112
	 */
113
	public function provider_getGoodTransaction(): array {
114
		return [[TransactionType::Buy], [TransactionType::Sell]];
115
	}
116
117
	public function test_getGoodTransaction_throws_if_port_does_not_have_good(): void {
118
		// New ports don't have any goods yet, so this will throw on any good
119
		$port = AbstractSmrPort::createPort(1, 1);
120
		$this->expectException(Exception::class);
121
		$this->expectExceptionMessage('Port does not trade goodID 3');
122
		$port->getGoodTransaction(GOODS_ORE);
123
	}
124
125
	public function test_shields(): void {
126
		$port = AbstractSmrPort::createPort(1, 1);
127
		// newly created ports start with no shields
128
		self::assertSame(0, $port->getShields());
129
		self::assertFalse($port->hasShields());
130
131
		// Test setting shields explicitly
132
		$port->setShields(100);
133
		self::assertSame(100, $port->getShields());
134
		self::assertTrue($port->hasShields());
135
136
		// Test decreasing shields
137
		$port->decreaseShields(2);
138
		self::assertSame(98, $port->getShields());
139
	}
140
141
	public function test_cds(): void {
142
		$port = AbstractSmrPort::createPort(1, 1);
143
		// newly created ports start with no CDs
144
		self::assertSame(0, $port->getCDs());
145
		self::assertFalse($port->hasCDs());
146
147
		// Test setting CDs explicitly
148
		$port->setCDs(100);
149
		self::assertSame(100, $port->getCDs());
150
		self::assertTrue($port->hasCDs());
151
152
		// Test decreasing CDs
153
		$port->decreaseCDs(2);
154
		self::assertSame(98, $port->getCDs());
155
	}
156
157
	public function test_armour(): void {
158
		$port = AbstractSmrPort::createPort(1, 1);
159
		// newly created ports start with no armour
160
		self::assertSame(0, $port->getArmour());
161
		self::assertFalse($port->hasArmour());
162
163
		// Test setting shields explicitly
164
		$port->setArmour(100);
165
		self::assertSame(100, $port->getArmour());
166
		self::assertTrue($port->hasArmour());
167
168
		// Test decreasing shields
169
		$port->decreaseArmour(2);
170
		self::assertSame(98, $port->getArmour());
171
	}
172
173
	/**
174
	 * @dataProvider dataProvider_takeDamage
175
	 *
176
	 * @param array<string, int|bool> $damage
177
	 * @param array<string, int|bool> $expected
178
	 */
179
	public function test_takeDamage(string $case, array $damage, array $expected, int $shields, int $cds, int $armour): void {
180
		// Set up a port with a fixed amount of defenses
181
		$port = AbstractSmrPort::createPort(1, 1);
182
		$port->setShields($shields);
183
		$port->setCDs($cds);
184
		$port->setArmour($armour);
185
		// Test taking damage
186
		$result = $port->takeDamage($damage);
187
		self::assertSame($expected, $result, $case);
188
	}
189
190
	/**
191
	 * @return array<array<mixed>>
192
	 */
193
	public function dataProvider_takeDamage(): array {
194
		return [
195
			[
196
				'Do overkill damage (e.g. 1000 drone damage)',
197
				[
198
					'Shield' => 1000,
199
					'Armour' => 1000,
200
					'Rollover' => true,
201
				],
202
				[
203
					'KillingShot' => true,
204
					'TargetAlreadyDead' => false,
205
					'Shield' => 100,
206
					'CDs' => 30,
207
					'NumCDs' => 10,
208
					'HasCDs' => false,
209
					'Armour' => 100,
210
					'TotalDamage' => 230,
211
				],
212
				100, 10, 100,
213
			],
214
			[
215
				'Do exactly lethal damage (e.g. 230 drone damage)',
216
				[
217
					'Shield' => 230,
218
					'Armour' => 230,
219
					'Rollover' => true,
220
				],
221
				[
222
					'KillingShot' => true,
223
					'TargetAlreadyDead' => false,
224
					'Shield' => 100,
225
					'CDs' => 30,
226
					'NumCDs' => 10,
227
					'HasCDs' => false,
228
					'Armour' => 100,
229
					'TotalDamage' => 230,
230
				],
231
				100, 10, 100,
232
			],
233
			[
234
				'Do damage to drones behind shields (e.g. armour-only weapon)',
235
				[
236
					'Shield' => 0,
237
					'Armour' => 100,
238
					'Rollover' => false,
239
				],
240
				[
241
					'KillingShot' => false,
242
					'TargetAlreadyDead' => false,
243
					'Shield' => 0,
244
					'CDs' => 18,
245
					'NumCDs' => 6,
246
					'HasCDs' => true,
247
					'Armour' => 0,
248
					'TotalDamage' => 18,
249
				],
250
				100, 10, 100,
251
			],
252
			[
253
				'Do NOT do damage to armour behind shields (e.g. armour-only weapon)',
254
				[
255
					'Shield' => 0,
256
					'Armour' => 100,
257
					'Rollover' => false,
258
				],
259
				[
260
					'KillingShot' => false,
261
					'TargetAlreadyDead' => false,
262
					'Shield' => 0,
263
					'CDs' => 0,
264
					'NumCDs' => 0,
265
					'HasCDs' => false,
266
					'Armour' => 0,
267
					'TotalDamage' => 0,
268
				],
269
				100, 0, 100,
270
			],
271
			[
272
				'Overkill shield damage only (e.g. shield/armour weapon)',
273
				[
274
					'Shield' => 150,
275
					'Armour' => 150,
276
					'Rollover' => false,
277
				],
278
				[
279
					'KillingShot' => false,
280
					'TargetAlreadyDead' => false,
281
					'Shield' => 100,
282
					'CDs' => 0,
283
					'NumCDs' => 0,
284
					'HasCDs' => true,
285
					'Armour' => 0,
286
					'TotalDamage' => 100,
287
				],
288
				100, 10, 100,
289
			],
290
			[
291
				'Overkill CD damage only (e.g. shield/armour weapon)',
292
				[
293
					'Shield' => 150,
294
					'Armour' => 150,
295
					'Rollover' => false,
296
				],
297
				[
298
					'KillingShot' => false,
299
					'TargetAlreadyDead' => false,
300
					'Shield' => 0,
301
					'CDs' => 30,
302
					'NumCDs' => 10,
303
					'HasCDs' => false,
304
					'Armour' => 0,
305
					'TotalDamage' => 30,
306
				],
307
				0, 10, 100,
308
			],
309
			[
310
				'Overkill armour damage only (e.g. shield/armour weapon)',
311
				[
312
					'Shield' => 150,
313
					'Armour' => 150,
314
					'Rollover' => false,
315
				],
316
				[
317
					'KillingShot' => true,
318
					'TargetAlreadyDead' => false,
319
					'Shield' => 0,
320
					'CDs' => 0,
321
					'NumCDs' => 0,
322
					'HasCDs' => false,
323
					'Armour' => 100,
324
					'TotalDamage' => 100,
325
				],
326
				0, 0, 100,
327
			],
328
			[
329
				'Target is already dead',
330
				[
331
					'Shield' => 100,
332
					'Armour' => 100,
333
					'Rollover' => true,
334
				],
335
				[
336
					'KillingShot' => false,
337
					'TargetAlreadyDead' => true,
338
					'Shield' => 0,
339
					'CDs' => 0,
340
					'NumCDs' => 0,
341
					'HasCDs' => false,
342
					'Armour' => 0,
343
					'TotalDamage' => 0,
344
				],
345
				0, 0, 0,
346
			],
347
		];
348
	}
349
350
	/**
351
	 * Ensure that the state of the port is self-consistent when it is
352
	 * destroyed and loses a level in the same attack.
353
	 */
354
	public function test_port_loses_level_on_raid_killshot(): void {
355
		// We're not testing database modifications, so stub it
356
		$db = $this->createStub(Database::class);
357
		DiContainer::getContainer()->set(Database::class, $db);
358
359
		// Add a few basic checks on the player that gets the killshot
360
		$player = $this->createMock(SmrPlayer::class);
361
		$player
362
			->expects(self::once())
363
			->method('decreaseRelations')
364
			->with(AbstractSmrPort::KILLER_RELATIONS_LOSS, RACE_NEUTRAL);
365
		$player
366
			->expects(self::once())
367
			->method('increaseCurrentBountyAmount')
368
			->with(BountyType::HQ, 0);
369
370
		// Make objects that must be accessed statically (can't be mocked)
371
		SmrSector::createSector(1, 1);
372
		SmrGame::createGame(1)->setGameTypeID(SmrGame::GAME_TYPE_DEFAULT);
373
374
		// Set up the port
375
		$portLevel = 3;
376
		$port = AbstractSmrPort::createPort(1, 1);
377
		$port->upgradeToLevel($portLevel);
378
379
		// Imitate the scenario of de-leveling a port in the same attack that
380
		// destroys the port. While there's a lot we could verify here, most
381
		// important is to make sure that it doesn't throw.
382
		$result = $port->killPortByPlayer($player);
383
		$port->upgradeToLevel($portLevel - 1);
384
		$port->update();
385
386
		// killPortByPlayer should always return an empty array
387
		self::assertSame([], $result);
388
	}
389
390
}
391