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 (#1498)
by Dan
04:47
created

AbstractSmrPortTest::test_setPortGoods()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 33
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 1
eloc 19
c 1
b 0
f 1
nc 1
nop 0
dl 0
loc 33
rs 9.6333
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_setPortGoods(): void {
126
		$port = AbstractSmrPort::createPort(1, 1);
127
		$port->setLevel(1);
128
129
		// By default, a port's goods are empty
130
		self::assertSame([], $port->getGoodTransactions());
131
132
		// If we try to add an out-of-order good, we don't modify the port
133
		// and we return false.
134
		$badGoods = [
135
			GOODS_TEXTILES => TransactionType::Buy,
136
		];
137
		self::assertFalse($port->setPortGoods($badGoods));
138
		self::assertSame([], $port->getGoodTransactions());
139
140
		// If we add valid goods, the port is properly modified
141
		// (A level 1 port requires 3 goods)
142
		$validGoods = [
143
			GOODS_WOOD => TransactionType::Buy,
144
			GOODS_FOOD => TransactionType::Buy,
145
			GOODS_ORE => TransactionType::Sell,
146
		];
147
		self::assertTrue($port->setPortGoods($validGoods));
148
		self::assertSame($validGoods, $port->getGoodTransactions());
149
150
		// If we specify new goods, they are completely overriden
151
		$validGoods2 = [
152
			GOODS_WOOD => TransactionType::Sell, // opposite transaction
153
			GOODS_FOOD => TransactionType::Buy, // same transaction
154
			GOODS_SLAVES => TransactionType::Buy, // different good
155
		];
156
		self::assertTrue($port->setPortGoods($validGoods2));
157
		self::assertSame($validGoods2, $port->getGoodTransactions());
158
	}
159
160
	public function test_shields(): void {
161
		$port = AbstractSmrPort::createPort(1, 1);
162
		// newly created ports start with no shields
163
		self::assertSame(0, $port->getShields());
164
		self::assertFalse($port->hasShields());
165
166
		// Test setting shields explicitly
167
		$port->setShields(100);
168
		self::assertSame(100, $port->getShields());
169
		self::assertTrue($port->hasShields());
170
171
		// Test decreasing shields
172
		$port->decreaseShields(2);
173
		self::assertSame(98, $port->getShields());
174
	}
175
176
	public function test_cds(): void {
177
		$port = AbstractSmrPort::createPort(1, 1);
178
		// newly created ports start with no CDs
179
		self::assertSame(0, $port->getCDs());
180
		self::assertFalse($port->hasCDs());
181
182
		// Test setting CDs explicitly
183
		$port->setCDs(100);
184
		self::assertSame(100, $port->getCDs());
185
		self::assertTrue($port->hasCDs());
186
187
		// Test decreasing CDs
188
		$port->decreaseCDs(2);
189
		self::assertSame(98, $port->getCDs());
190
	}
191
192
	public function test_armour(): void {
193
		$port = AbstractSmrPort::createPort(1, 1);
194
		// newly created ports start with no armour
195
		self::assertSame(0, $port->getArmour());
196
		self::assertFalse($port->hasArmour());
197
198
		// Test setting shields explicitly
199
		$port->setArmour(100);
200
		self::assertSame(100, $port->getArmour());
201
		self::assertTrue($port->hasArmour());
202
203
		// Test decreasing shields
204
		$port->decreaseArmour(2);
205
		self::assertSame(98, $port->getArmour());
206
	}
207
208
	/**
209
	 * @dataProvider dataProvider_takeDamage
210
	 *
211
	 * @param array<string, int|bool> $damage
212
	 * @param array<string, int|bool> $expected
213
	 */
214
	public function test_takeDamage(string $case, array $damage, array $expected, int $shields, int $cds, int $armour): void {
215
		// Set up a port with a fixed amount of defenses
216
		$port = AbstractSmrPort::createPort(1, 1);
217
		$port->setShields($shields);
218
		$port->setCDs($cds);
219
		$port->setArmour($armour);
220
		// Test taking damage
221
		$result = $port->takeDamage($damage);
222
		self::assertSame($expected, $result, $case);
223
	}
224
225
	/**
226
	 * @return array<array<mixed>>
227
	 */
228
	public function dataProvider_takeDamage(): array {
229
		return [
230
			[
231
				'Do overkill damage (e.g. 1000 drone damage)',
232
				[
233
					'Shield' => 1000,
234
					'Armour' => 1000,
235
					'Rollover' => true,
236
				],
237
				[
238
					'KillingShot' => true,
239
					'TargetAlreadyDead' => false,
240
					'Shield' => 100,
241
					'CDs' => 30,
242
					'NumCDs' => 10,
243
					'HasCDs' => false,
244
					'Armour' => 100,
245
					'TotalDamage' => 230,
246
				],
247
				100, 10, 100,
248
			],
249
			[
250
				'Do exactly lethal damage (e.g. 230 drone damage)',
251
				[
252
					'Shield' => 230,
253
					'Armour' => 230,
254
					'Rollover' => true,
255
				],
256
				[
257
					'KillingShot' => true,
258
					'TargetAlreadyDead' => false,
259
					'Shield' => 100,
260
					'CDs' => 30,
261
					'NumCDs' => 10,
262
					'HasCDs' => false,
263
					'Armour' => 100,
264
					'TotalDamage' => 230,
265
				],
266
				100, 10, 100,
267
			],
268
			[
269
				'Do damage to drones behind shields (e.g. armour-only weapon)',
270
				[
271
					'Shield' => 0,
272
					'Armour' => 100,
273
					'Rollover' => false,
274
				],
275
				[
276
					'KillingShot' => false,
277
					'TargetAlreadyDead' => false,
278
					'Shield' => 0,
279
					'CDs' => 18,
280
					'NumCDs' => 6,
281
					'HasCDs' => true,
282
					'Armour' => 0,
283
					'TotalDamage' => 18,
284
				],
285
				100, 10, 100,
286
			],
287
			[
288
				'Do NOT do damage to armour behind shields (e.g. armour-only weapon)',
289
				[
290
					'Shield' => 0,
291
					'Armour' => 100,
292
					'Rollover' => false,
293
				],
294
				[
295
					'KillingShot' => false,
296
					'TargetAlreadyDead' => false,
297
					'Shield' => 0,
298
					'CDs' => 0,
299
					'NumCDs' => 0,
300
					'HasCDs' => false,
301
					'Armour' => 0,
302
					'TotalDamage' => 0,
303
				],
304
				100, 0, 100,
305
			],
306
			[
307
				'Overkill shield damage only (e.g. shield/armour weapon)',
308
				[
309
					'Shield' => 150,
310
					'Armour' => 150,
311
					'Rollover' => false,
312
				],
313
				[
314
					'KillingShot' => false,
315
					'TargetAlreadyDead' => false,
316
					'Shield' => 100,
317
					'CDs' => 0,
318
					'NumCDs' => 0,
319
					'HasCDs' => true,
320
					'Armour' => 0,
321
					'TotalDamage' => 100,
322
				],
323
				100, 10, 100,
324
			],
325
			[
326
				'Overkill CD damage only (e.g. shield/armour weapon)',
327
				[
328
					'Shield' => 150,
329
					'Armour' => 150,
330
					'Rollover' => false,
331
				],
332
				[
333
					'KillingShot' => false,
334
					'TargetAlreadyDead' => false,
335
					'Shield' => 0,
336
					'CDs' => 30,
337
					'NumCDs' => 10,
338
					'HasCDs' => false,
339
					'Armour' => 0,
340
					'TotalDamage' => 30,
341
				],
342
				0, 10, 100,
343
			],
344
			[
345
				'Overkill armour damage only (e.g. shield/armour weapon)',
346
				[
347
					'Shield' => 150,
348
					'Armour' => 150,
349
					'Rollover' => false,
350
				],
351
				[
352
					'KillingShot' => true,
353
					'TargetAlreadyDead' => false,
354
					'Shield' => 0,
355
					'CDs' => 0,
356
					'NumCDs' => 0,
357
					'HasCDs' => false,
358
					'Armour' => 100,
359
					'TotalDamage' => 100,
360
				],
361
				0, 0, 100,
362
			],
363
			[
364
				'Target is already dead',
365
				[
366
					'Shield' => 100,
367
					'Armour' => 100,
368
					'Rollover' => true,
369
				],
370
				[
371
					'KillingShot' => false,
372
					'TargetAlreadyDead' => true,
373
					'Shield' => 0,
374
					'CDs' => 0,
375
					'NumCDs' => 0,
376
					'HasCDs' => false,
377
					'Armour' => 0,
378
					'TotalDamage' => 0,
379
				],
380
				0, 0, 0,
381
			],
382
		];
383
	}
384
385
	/**
386
	 * Ensure that the state of the port is self-consistent when it is
387
	 * destroyed and loses a level in the same attack.
388
	 */
389
	public function test_port_loses_level_on_raid_killshot(): void {
390
		// We're not testing database modifications, so stub it
391
		$db = $this->createStub(Database::class);
392
		DiContainer::getContainer()->set(Database::class, $db);
393
394
		// Add a few basic checks on the player that gets the killshot
395
		$player = $this->createMock(SmrPlayer::class);
396
		$player
397
			->expects(self::once())
398
			->method('decreaseRelations')
399
			->with(AbstractSmrPort::KILLER_RELATIONS_LOSS, RACE_NEUTRAL);
400
		$player
401
			->expects(self::once())
402
			->method('increaseCurrentBountyAmount')
403
			->with(BountyType::HQ, 0);
404
405
		// Make objects that must be accessed statically (can't be mocked)
406
		SmrSector::createSector(1, 1);
407
		SmrGame::createGame(1)->setGameTypeID(SmrGame::GAME_TYPE_DEFAULT);
408
409
		// Set up the port
410
		$portLevel = 3;
411
		$port = AbstractSmrPort::createPort(1, 1);
412
		$port->upgradeToLevel($portLevel);
413
414
		// Imitate the scenario of de-leveling a port in the same attack that
415
		// destroys the port. While there's a lot we could verify here, most
416
		// important is to make sure that it doesn't throw.
417
		$result = $port->killPortByPlayer($player);
418
		$port->upgradeToLevel($portLevel - 1);
419
		$port->update();
420
421
		// killPortByPlayer should always return an empty array
422
		self::assertSame([], $result);
423
	}
424
425
}
426