Add repo hygiene rules and ignore secrets

This commit is contained in:
2026-02-03 06:55:39 +01:00
parent 88732a8ae7
commit 6035bc1715
52 changed files with 3295 additions and 25 deletions

View File

@@ -0,0 +1,89 @@
<?php
declare(strict_types=1);
namespace App\Tests\Integration;
use App\Shared\Clock\FixedTimeProvider;
use App\Tests\Support\TestAppFactory;
use App\Tests\Support\TestDatabase;
use PHPUnit\Framework\TestCase;
use Slim\Psr7\Factory\ServerRequestFactory;
final class BuildStartTest extends TestCase
{
public function testBuildStartAndFinalize(): void
{
$pdo = TestDatabase::create();
TestDatabase::reset($pdo);
$seed = TestDatabase::seedMinimal($pdo);
$userId = (int)$seed['user_id'];
$resources = [
'metal' => 1000.0,
'alloy' => 0.0,
'crystals' => 1000.0,
'energy' => 0.0,
'credits' => 0.0,
'population' => 0.0,
'water' => 0.0,
'deuterium' => 0.0,
'food' => 0.0,
];
$stmt = $pdo->prepare(
'INSERT INTO planets (user_id, name, class_key, planet_seed, temperature_c, modifiers, resources, last_resource_update_at)
VALUES (:user_id, :name, :class_key, :planet_seed, :temperature_c, :modifiers, :resources, :last_update)
RETURNING id'
);
$stmt->execute([
'user_id' => $userId,
'name' => 'Testworld',
'class_key' => 'temperate',
'planet_seed' => 7,
'temperature_c' => 18,
'modifiers' => json_encode(['metal' => 0.0], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
'resources' => json_encode($resources, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
'last_update' => '2026-02-03 00:00:00',
]);
$planetId = (int)$stmt->fetchColumn();
$stmt = $pdo->prepare(
'INSERT INTO planet_buildings (planet_id, building_key, count, level)
VALUES (:planet_id, :building_key, :count, 0)'
);
$stmt->execute(['planet_id' => $planetId, 'building_key' => 'build_center', 'count' => 1]);
$time = new FixedTimeProvider(new \DateTimeImmutable('2026-02-03 00:00:00'));
$app = TestAppFactory::create($pdo, $time);
$factory = new ServerRequestFactory();
$request = $factory->createServerRequest('POST', '/build/start')
->withHeader('Content-Type', 'application/json')
->withHeader('X-User-Id', (string)$userId);
$request->getBody()->write(json_encode([
'building_key' => 'ore_mine',
'amount' => 1,
'planet_id' => $planetId,
]));
$request->getBody()->rewind();
$response = $app->handle($request);
self::assertSame(201, $response->getStatusCode());
$body = json_decode((string)$response->getBody(), true);
self::assertSame(880.0, $body['resources']['metal']);
self::assertSame(940.0, $body['resources']['crystals']);
$time->setNow(new \DateTimeImmutable('2026-02-03 00:02:00'));
$jobsRequest = $factory->createServerRequest('GET', '/build/jobs')
->withHeader('X-User-Id', (string)$userId);
$jobsResponse = $app->handle($jobsRequest);
self::assertSame(200, $jobsResponse->getStatusCode());
$count = (int)$pdo->query("SELECT count FROM planet_buildings WHERE planet_id = {$planetId} AND building_key = 'ore_mine'")->fetchColumn();
self::assertSame(1, $count);
}
}

View File

@@ -0,0 +1,62 @@
<?php
declare(strict_types=1);
namespace App\Tests\Integration;
use App\Shared\Clock\FixedTimeProvider;
use App\Tests\Support\TestAppFactory;
use App\Tests\Support\TestDatabase;
use PHPUnit\Framework\TestCase;
use Slim\Psr7\Factory\ServerRequestFactory;
final class PermissionDenyTest extends TestCase
{
public function testDenyOverrideBlocksPermission(): void
{
$pdo = TestDatabase::create();
TestDatabase::reset($pdo);
$seed = TestDatabase::seedMinimal($pdo);
$userId = (int)$seed['user_id'];
$resources = [
'metal' => 100.0,
'alloy' => 0.0,
'crystals' => 50.0,
'energy' => 0.0,
'credits' => 0.0,
'population' => 0.0,
'water' => 0.0,
'deuterium' => 0.0,
'food' => 0.0,
];
$stmt = $pdo->prepare(
'INSERT INTO planets (user_id, name, class_key, planet_seed, temperature_c, modifiers, resources, last_resource_update_at)
VALUES (:user_id, :name, :class_key, :planet_seed, :temperature_c, :modifiers, :resources, :last_update)'
);
$stmt->execute([
'user_id' => $userId,
'name' => 'Denied',
'class_key' => 'temperate',
'planet_seed' => 9,
'temperature_c' => 12,
'modifiers' => json_encode(['metal' => 0.0], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
'resources' => json_encode($resources, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
'last_update' => '2026-02-03 00:00:00',
]);
$permissionId = (int)$pdo->query("SELECT id FROM permissions WHERE key = 'planet.public.view'")->fetchColumn();
$pdo->exec("INSERT INTO user_permission_overrides (user_id, permission_id, effect) VALUES ({$userId}, {$permissionId}, 'deny')");
$time = new FixedTimeProvider(new \DateTimeImmutable('2026-02-03 00:00:00'));
$app = TestAppFactory::create($pdo, $time);
$factory = new ServerRequestFactory();
$request = $factory->createServerRequest('GET', '/state')
->withHeader('X-User-Id', (string)$userId);
$response = $app->handle($request);
self::assertSame(403, $response->getStatusCode());
}
}

View File

@@ -0,0 +1,27 @@
<?php
declare(strict_types=1);
namespace App\Tests\Support;
use App\Bootstrap;
use App\Config\ConfigLoader;
use App\Config\ConfigValidator;
use App\Shared\Clock\TimeProvider;
use Psr\Container\ContainerInterface;
use Slim\App;
final class TestAppFactory
{
public static function create(\PDO $pdo, TimeProvider $timeProvider): App
{
$repoRoot = dirname(__DIR__, 3);
$container = Bootstrap::buildContainer([
\PDO::class => $pdo,
TimeProvider::class => $timeProvider,
ConfigLoader::class => new ConfigLoader(new ConfigValidator(), $repoRoot . '/config'),
]);
return Bootstrap::createApp($container);
}
}

View File

@@ -0,0 +1,62 @@
<?php
declare(strict_types=1);
namespace App\Tests\Support;
use App\Database\ConnectionFactory;
use PDO;
final class TestDatabase
{
public static function create(): PDO
{
$dbName = getenv('DB_TEST_NAME') ?: (getenv('DB_NAME') ?: 'galaxyforge');
return ConnectionFactory::create($dbName);
}
public static function reset(PDO $pdo): void
{
$tables = [
'user_permission_overrides',
'user_roles',
'role_permissions',
'permissions',
'roles',
'build_jobs',
'planet_buildings',
'planets',
'users',
];
foreach ($tables as $table) {
$pdo->exec("DROP TABLE IF EXISTS {$table} CASCADE");
}
$migration = __DIR__ . '/../../db/migrations/001_init.sql';
$sql = file_get_contents($migration);
if ($sql === false) {
throw new \RuntimeException('Migration nicht lesbar.');
}
$pdo->exec($sql);
}
public static function seedMinimal(PDO $pdo): array
{
$pdo->exec("INSERT INTO roles (key, name) VALUES ('player', 'Spieler')");
$pdo->exec("INSERT INTO roles (key, name) VALUES ('admin', 'Admin')");
$pdo->exec("INSERT INTO permissions (key, module, description) VALUES ('planet.public.view', 'planet', 'Planet ansehen')");
$pdo->exec("INSERT INTO permissions (key, module, description) VALUES ('planet.admin.generate', 'planet', 'Planeten generieren')");
$pdo->exec("INSERT INTO role_permissions (role_id, permission_id)
SELECT r.id, p.id FROM roles r JOIN permissions p ON r.key = 'player' AND p.key = 'planet.public.view'");
$pdo->exec("INSERT INTO users (username, race_key) VALUES ('tester', 'human')");
$userId = (int)$pdo->query("SELECT id FROM users WHERE username = 'tester'")->fetchColumn();
$pdo->exec("INSERT INTO user_roles (user_id, role_id)
SELECT {$userId}, r.id FROM roles r WHERE r.key = 'player'");
return ['user_id' => $userId];
}
}

View File

@@ -0,0 +1,17 @@
<?php
declare(strict_types=1);
namespace App\Tests\Unit;
use App\Module\Economy\Service\EconomyService;
use PHPUnit\Framework\TestCase;
final class MultiplierTest extends TestCase
{
public function testMultiplierChain(): void
{
$result = EconomyService::multiplyBonuses([0.10, -0.15, 0.10, 0.02]);
self::assertEquals(1.04907, $result, '', 0.0001);
}
}

View File

@@ -0,0 +1,34 @@
<?php
declare(strict_types=1);
namespace App\Tests\Unit;
use App\Config\ConfigLoader;
use App\Config\ConfigValidator;
use App\Module\PlanetGenerator\Service\PlanetGenerator;
use PHPUnit\Framework\TestCase;
final class PlanetGeneratorTest extends TestCase
{
public function testIceConstraintsAndScore(): void
{
$loader = new ConfigLoader(new ConfigValidator(), dirname(__DIR__, 3) . '/config');
$generator = new PlanetGenerator($loader);
$result = $generator->generate('ice', 'normal', 1234);
$mods = $result['modifiers'];
self::assertGreaterThanOrEqual(0.5, $mods['water']);
self::assertLessThanOrEqual(-0.6, $mods['energy']);
self::assertGreaterThanOrEqual(-80, $result['temperature_c']);
self::assertLessThanOrEqual(-10, $result['temperature_c']);
$config = $loader->planetClasses();
$target = (float)$config['tiers']['normal']['target_score'];
$epsilon = (float)$config['tiers']['normal']['epsilon'];
$score = $generator->calculateScore($mods);
self::assertEquals($target, $score, '', $epsilon);
}
}

View File

@@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
namespace App\Tests\Unit;
use App\Config\ConfigLoader;
use App\Config\ConfigValidator;
use App\Module\Blueprints\Service\BlueprintService;
use App\Module\BuildQueue\Service\BuildQueueService;
use App\Shared\Clock\FixedTimeProvider;
use PHPUnit\Framework\TestCase;
final class QueueSlotsTest extends TestCase
{
public function testQueueSlotsGrowWithBuildCenter(): void
{
$pdo = new \PDO('sqlite::memory:');
$loader = new ConfigLoader(new ConfigValidator(), dirname(__DIR__, 3) . '/config');
$blueprints = new BlueprintService($loader);
$service = new BuildQueueService($pdo, $blueprints, new FixedTimeProvider(new \DateTimeImmutable('2026-02-03 00:00:00')));
$buildings = [
'build_center' => ['count' => 2, 'level' => 0],
];
$slots = $service->calculateQueueSlots($buildings, 0);
self::assertSame(2, $slots);
}
}

View File

@@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
require __DIR__ . '/../vendor/autoload.php';
$root = dirname(__DIR__, 1);
$repoRoot = dirname($root, 1);
if (file_exists($repoRoot . '/.env')) {
$dotenv = Dotenv\Dotenv::createImmutable($repoRoot);
$dotenv->safeLoad();
}
if (!getenv('APP_ENV')) {
putenv('APP_ENV=test');
}
if (!getenv('DEV_MODE')) {
putenv('DEV_MODE=1');
}