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 — main ( 2c9538...02418a )
by Dan
38s queued 18s
created

AbstractSmrPortTest::tearDownAfterClass()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

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