API updadte
This commit is contained in:
10
.gitignore
vendored
10
.gitignore
vendored
@@ -84,3 +84,13 @@ docker-compose.override.yml
|
||||
/var/
|
||||
/vendor/
|
||||
###< symfony/framework-bundle ###
|
||||
|
||||
###> phpunit/phpunit ###
|
||||
/phpunit.xml
|
||||
/.phpunit.cache/
|
||||
###< phpunit/phpunit ###
|
||||
|
||||
###> friendsofphp/php-cs-fixer ###
|
||||
/.php-cs-fixer.php
|
||||
/.php-cs-fixer.cache
|
||||
###< friendsofphp/php-cs-fixer ###
|
||||
|
||||
30
.php-cs-fixer.dist.php
Normal file
30
.php-cs-fixer.dist.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
$finder = (new PhpCsFixer\Finder())
|
||||
->in(__DIR__)
|
||||
->exclude('var')
|
||||
->exclude('vendor')
|
||||
->exclude('migrations')
|
||||
->notPath('bin/console')
|
||||
->notPath('public/index.php')
|
||||
;
|
||||
|
||||
return (new PhpCsFixer\Config())
|
||||
->setRules([
|
||||
'@Symfony' => true,
|
||||
'array_syntax' => ['syntax' => 'short'],
|
||||
'ordered_imports' => ['sort_algorithm' => 'alpha'],
|
||||
'no_unused_imports' => true,
|
||||
'not_operator_with_successor_space' => true,
|
||||
'trailing_comma_in_multiline' => true,
|
||||
'phpdoc_scalar' => true,
|
||||
'unary_operator_spaces' => true,
|
||||
'binary_operator_spaces' => true,
|
||||
'blank_line_before_statement' => [
|
||||
'statements' => ['break', 'continue', 'declare', 'return', 'throw', 'try'],
|
||||
],
|
||||
'phpdoc_single_line_var_spacing' => true,
|
||||
'phpdoc_var_without_name' => true,
|
||||
])
|
||||
->setFinder($finder)
|
||||
;
|
||||
102
README.md
102
README.md
@@ -14,6 +14,8 @@ Eine moderne Webanwendung zur Immobilienberechnung, entwickelt mit Symfony 7.3,
|
||||
- **Öffentliche Ressourcen**: Bundesländer und Heizungstypen ohne Authentifizierung abrufbar
|
||||
- **Docker-Setup**: Vollständig containerisierte Entwicklungsumgebung
|
||||
- **Datenbank-Migrationen**: Versionskontrollierte Datenbankschema-Verwaltung mit Doctrine
|
||||
- **Testing**: Umfassende Test-Suite mit PHPUnit für Unit- und Funktions-Tests
|
||||
- **Code Quality**: PHP-CS-Fixer für konsistenten Code-Style nach Symfony Standards
|
||||
- **CORS-Unterstützung**: Konfigurierbare CORS-Einstellungen für API-Zugriffe
|
||||
- **phpMyAdmin**: Integriertes Datenbank-Verwaltungstool
|
||||
|
||||
@@ -182,6 +184,106 @@ Wichtigste Endpunkte:
|
||||
- Grundbuchkosten: ca. 0,5% des Kaufpreises
|
||||
- Grunderwerbsteuer: abhängig vom Bundesland (3,5% - 6,5%)
|
||||
|
||||
## Testing & Code Quality
|
||||
|
||||
Das Projekt verwendet **PHPUnit** für Unit- und Funktionstests sowie **PHP-CS-Fixer** für Code-Qualität und Linting.
|
||||
|
||||
### Tests ausführen
|
||||
|
||||
#### Alle Tests ausführen
|
||||
|
||||
```bash
|
||||
docker-compose exec web php bin/phpunit
|
||||
```
|
||||
|
||||
#### Tests mit Ausgabedetails
|
||||
|
||||
```bash
|
||||
docker-compose exec web php bin/phpunit --verbose
|
||||
```
|
||||
|
||||
#### Nur bestimmte Testklassen ausführen
|
||||
|
||||
```bash
|
||||
# Entity-Tests
|
||||
docker-compose exec web php bin/phpunit tests/Entity
|
||||
|
||||
# API-Tests
|
||||
docker-compose exec web php bin/phpunit tests/Api
|
||||
|
||||
# Einzelne Testklasse
|
||||
docker-compose exec web php bin/phpunit tests/Entity/UserTest.php
|
||||
```
|
||||
|
||||
#### Code Coverage Report (optional)
|
||||
|
||||
```bash
|
||||
docker-compose exec web php bin/phpunit --coverage-text
|
||||
```
|
||||
|
||||
### Code-Linting mit PHP-CS-Fixer
|
||||
|
||||
#### Code-Style prüfen (Dry-Run)
|
||||
|
||||
```bash
|
||||
docker-compose exec web vendor/bin/php-cs-fixer fix --dry-run --diff
|
||||
```
|
||||
|
||||
#### Code automatisch formatieren
|
||||
|
||||
```bash
|
||||
docker-compose exec web vendor/bin/php-cs-fixer fix
|
||||
```
|
||||
|
||||
#### Bestimmte Verzeichnisse prüfen
|
||||
|
||||
```bash
|
||||
# Nur src/ Verzeichnis
|
||||
docker-compose exec web vendor/bin/php-cs-fixer fix src --dry-run
|
||||
|
||||
# Nur tests/ Verzeichnis
|
||||
docker-compose exec web vendor/bin/php-cs-fixer fix tests --dry-run
|
||||
```
|
||||
|
||||
### Test-Struktur
|
||||
|
||||
```
|
||||
tests/
|
||||
├── Entity/ # Unit-Tests für Entities
|
||||
│ ├── UserTest.php # User-Entity Tests
|
||||
│ ├── ImmobilieTest.php # Immobilie-Entity Tests (inkl. Kaufnebenkosten)
|
||||
│ ├── BundeslandTest.php # Bundesland-Entity Tests
|
||||
│ └── HeizungstypTest.php # Heizungstyp-Entity Tests
|
||||
└── Api/ # Funktions-/API-Tests
|
||||
├── BundeslandApiTest.php # Bundesländer API-Tests
|
||||
├── HeizungstypApiTest.php # Heizungstypen API-Tests
|
||||
└── ApiDocumentationTest.php # API-Dokumentations-Tests
|
||||
```
|
||||
|
||||
### Test-Abdeckung
|
||||
|
||||
Die Tests decken folgende Bereiche ab:
|
||||
|
||||
**Entity-Tests:**
|
||||
- User-Entity: API-Key-Generierung, Rollen, UserInterface-Methoden
|
||||
- Immobilie-Entity: Gesamtflächen-Berechnung, Kaufnebenkosten-Berechnung
|
||||
- Bundesland-Entity: Grunderwerbsteuer-Werte für alle Bundesländer
|
||||
- Heizungstyp-Entity: CRUD-Operationen
|
||||
|
||||
**API-Tests:**
|
||||
- Öffentlicher Zugriff auf Bundesländer und Heizungstypen (GET)
|
||||
- Authentifizierung für CREATE-Operationen
|
||||
- API-Dokumentation Zugänglichkeit
|
||||
|
||||
### Code-Style Regeln
|
||||
|
||||
Die PHP-CS-Fixer-Konfiguration (`.php-cs-fixer.dist.php`) verwendet:
|
||||
- Symfony Coding Standards
|
||||
- Short Array Syntax
|
||||
- Sortierte Imports
|
||||
- Trailing Commas in Multiline Arrays
|
||||
- Und weitere PSR-12 kompatible Regeln
|
||||
|
||||
## Entwicklung
|
||||
|
||||
### Neue Entity erstellen
|
||||
|
||||
4
bin/phpunit
Normal file
4
bin/phpunit
Normal file
@@ -0,0 +1,4 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
require dirname(__DIR__).'/vendor/phpunit/phpunit/phpunit';
|
||||
@@ -45,5 +45,11 @@
|
||||
"cache:clear": "symfony-cmd",
|
||||
"assets:install %PUBLIC_DIR%": "symfony-cmd"
|
||||
}
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^12.4",
|
||||
"symfony/browser-kit": "^7.3",
|
||||
"symfony/css-selector": "^7.3",
|
||||
"friendsofphp/php-cs-fixer": "^3.89"
|
||||
}
|
||||
}
|
||||
|
||||
3281
composer.lock
generated
3281
composer.lock
generated
File diff suppressed because it is too large
Load Diff
44
phpunit.dist.xml
Normal file
44
phpunit.dist.xml
Normal file
@@ -0,0 +1,44 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<!-- https://phpunit.readthedocs.io/en/latest/configuration.html -->
|
||||
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
|
||||
colors="true"
|
||||
failOnDeprecation="true"
|
||||
failOnNotice="true"
|
||||
failOnWarning="true"
|
||||
bootstrap="tests/bootstrap.php"
|
||||
cacheDirectory=".phpunit.cache"
|
||||
>
|
||||
<php>
|
||||
<ini name="display_errors" value="1" />
|
||||
<ini name="error_reporting" value="-1" />
|
||||
<server name="APP_ENV" value="test" force="true" />
|
||||
<server name="SHELL_VERBOSITY" value="-1" />
|
||||
</php>
|
||||
|
||||
<testsuites>
|
||||
<testsuite name="Project Test Suite">
|
||||
<directory>tests</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
|
||||
<source ignoreSuppressionOfDeprecations="true"
|
||||
ignoreIndirectDeprecations="true"
|
||||
restrictNotices="true"
|
||||
restrictWarnings="true"
|
||||
>
|
||||
<include>
|
||||
<directory>src</directory>
|
||||
</include>
|
||||
|
||||
<deprecationTrigger>
|
||||
<method>Doctrine\Deprecations\Deprecation::trigger</method>
|
||||
<method>Doctrine\Deprecations\Deprecation::delegateTriggerToBackend</method>
|
||||
<function>trigger_deprecation</function>
|
||||
</deprecationTrigger>
|
||||
</source>
|
||||
|
||||
<extensions>
|
||||
</extensions>
|
||||
</phpunit>
|
||||
@@ -19,12 +19,12 @@ class CurrentUserExtension implements QueryCollectionExtensionInterface, QueryIt
|
||||
) {
|
||||
}
|
||||
|
||||
public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, Operation $operation = null, array $context = []): void
|
||||
public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, ?Operation $operation = null, array $context = []): void
|
||||
{
|
||||
$this->addWhere($queryBuilder, $resourceClass);
|
||||
}
|
||||
|
||||
public function applyToItem(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, array $identifiers, Operation $operation = null, array $context = []): void
|
||||
public function applyToItem(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, array $identifiers, ?Operation $operation = null, array $context = []): void
|
||||
{
|
||||
$this->addWhere($queryBuilder, $resourceClass);
|
||||
}
|
||||
@@ -39,13 +39,14 @@ class CurrentUserExtension implements QueryCollectionExtensionInterface, QueryIt
|
||||
$user = $this->security->getUser();
|
||||
|
||||
// Wenn nicht eingeloggt, keine Ergebnisse
|
||||
if (!$user instanceof User) {
|
||||
if (! $user instanceof User) {
|
||||
$queryBuilder->andWhere('1 = 0');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Admin sieht alles
|
||||
if ($user->getRole() === UserRole::ADMIN) {
|
||||
if (UserRole::ADMIN === $user->getRole()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
namespace App\Entity;
|
||||
|
||||
use ApiPlatform\Metadata\ApiResource;
|
||||
use ApiPlatform\Metadata\Delete;
|
||||
use ApiPlatform\Metadata\Get;
|
||||
use ApiPlatform\Metadata\GetCollection;
|
||||
use ApiPlatform\Metadata\Post;
|
||||
use ApiPlatform\Metadata\Put;
|
||||
use ApiPlatform\Metadata\Delete;
|
||||
use App\Repository\BundeslandRepository;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
@@ -20,7 +20,7 @@ use Symfony\Component\Validator\Constraints as Assert;
|
||||
new GetCollection(security: 'is_granted("PUBLIC_ACCESS")'),
|
||||
new Post(security: 'is_granted("ROLE_ADMIN") or is_granted("ROLE_TECHNICAL")'),
|
||||
new Put(security: 'is_granted("ROLE_ADMIN") or is_granted("ROLE_TECHNICAL")'),
|
||||
new Delete(security: 'is_granted("ROLE_ADMIN") or is_granted("ROLE_TECHNICAL")')
|
||||
new Delete(security: 'is_granted("ROLE_ADMIN") or is_granted("ROLE_TECHNICAL")'),
|
||||
]
|
||||
)]
|
||||
class Bundesland
|
||||
@@ -53,6 +53,7 @@ class Bundesland
|
||||
public function setName(string $name): self
|
||||
{
|
||||
$this->name = $name;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -64,6 +65,7 @@ class Bundesland
|
||||
public function setGrunderwerbsteuer(float $grunderwerbsteuer): self
|
||||
{
|
||||
$this->grunderwerbsteuer = $grunderwerbsteuer;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
namespace App\Entity;
|
||||
|
||||
use ApiPlatform\Metadata\ApiResource;
|
||||
use ApiPlatform\Metadata\Delete;
|
||||
use ApiPlatform\Metadata\Get;
|
||||
use ApiPlatform\Metadata\GetCollection;
|
||||
use ApiPlatform\Metadata\Post;
|
||||
use ApiPlatform\Metadata\Put;
|
||||
use ApiPlatform\Metadata\Delete;
|
||||
use App\Repository\HeizungstypRepository;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
@@ -20,7 +20,7 @@ use Symfony\Component\Validator\Constraints as Assert;
|
||||
new GetCollection(security: 'is_granted("PUBLIC_ACCESS")'),
|
||||
new Post(security: 'is_granted("ROLE_ADMIN") or is_granted("ROLE_TECHNICAL")'),
|
||||
new Put(security: 'is_granted("ROLE_ADMIN") or is_granted("ROLE_TECHNICAL")'),
|
||||
new Delete(security: 'is_granted("ROLE_ADMIN") or is_granted("ROLE_TECHNICAL")')
|
||||
new Delete(security: 'is_granted("ROLE_ADMIN") or is_granted("ROLE_TECHNICAL")'),
|
||||
]
|
||||
)]
|
||||
class Heizungstyp
|
||||
@@ -48,6 +48,7 @@ class Heizungstyp
|
||||
public function setName(string $name): self
|
||||
{
|
||||
$this->name = $name;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
namespace App\Entity;
|
||||
|
||||
use ApiPlatform\Metadata\ApiResource;
|
||||
use ApiPlatform\Metadata\Delete;
|
||||
use ApiPlatform\Metadata\Get;
|
||||
use ApiPlatform\Metadata\GetCollection;
|
||||
use ApiPlatform\Metadata\Post;
|
||||
use ApiPlatform\Metadata\Patch;
|
||||
use ApiPlatform\Metadata\Delete;
|
||||
use ApiPlatform\Metadata\Post;
|
||||
use App\Enum\ImmobilienTyp;
|
||||
use App\Repository\ImmobilieRepository;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
@@ -22,7 +22,7 @@ use Symfony\Component\Validator\Constraints as Assert;
|
||||
new GetCollection(),
|
||||
new Post(),
|
||||
new Patch(security: 'is_granted("edit", object)'),
|
||||
new Delete(security: 'is_granted("delete", object)')
|
||||
new Delete(security: 'is_granted("delete", object)'),
|
||||
],
|
||||
security: 'is_granted("ROLE_USER")'
|
||||
)]
|
||||
@@ -125,6 +125,7 @@ class Immobilie
|
||||
public function setAdresse(string $adresse): self
|
||||
{
|
||||
$this->adresse = $adresse;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -136,6 +137,7 @@ class Immobilie
|
||||
public function setWohnflaeche(int $wohnflaeche): self
|
||||
{
|
||||
$this->wohnflaeche = $wohnflaeche;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -147,6 +149,7 @@ class Immobilie
|
||||
public function setNutzflaeche(int $nutzflaeche): self
|
||||
{
|
||||
$this->nutzflaeche = $nutzflaeche;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -163,6 +166,7 @@ class Immobilie
|
||||
public function setGarage(bool $garage): self
|
||||
{
|
||||
$this->garage = $garage;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -174,6 +178,7 @@ class Immobilie
|
||||
public function setZimmer(int $zimmer): self
|
||||
{
|
||||
$this->zimmer = $zimmer;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -185,6 +190,7 @@ class Immobilie
|
||||
public function setBaujahr(?int $baujahr): self
|
||||
{
|
||||
$this->baujahr = $baujahr;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -196,6 +202,7 @@ class Immobilie
|
||||
public function setTyp(ImmobilienTyp $typ): self
|
||||
{
|
||||
$this->typ = $typ;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -207,6 +214,7 @@ class Immobilie
|
||||
public function setBeschreibung(?string $beschreibung): self
|
||||
{
|
||||
$this->beschreibung = $beschreibung;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -218,6 +226,7 @@ class Immobilie
|
||||
public function setEtage(?int $etage): self
|
||||
{
|
||||
$this->etage = $etage;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -229,6 +238,7 @@ class Immobilie
|
||||
public function setHeizungstyp(?Heizungstyp $heizungstyp): self
|
||||
{
|
||||
$this->heizungstyp = $heizungstyp;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -240,6 +250,7 @@ class Immobilie
|
||||
public function setCreatedAt(\DateTimeInterface $createdAt): self
|
||||
{
|
||||
$this->createdAt = $createdAt;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -251,6 +262,7 @@ class Immobilie
|
||||
public function setUpdatedAt(\DateTimeInterface $updatedAt): self
|
||||
{
|
||||
$this->updatedAt = $updatedAt;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -262,6 +274,7 @@ class Immobilie
|
||||
public function setAbschreibungszeit(?int $abschreibungszeit): self
|
||||
{
|
||||
$this->abschreibungszeit = $abschreibungszeit;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -273,6 +286,7 @@ class Immobilie
|
||||
public function setBundesland(?Bundesland $bundesland): self
|
||||
{
|
||||
$this->bundesland = $bundesland;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -284,6 +298,7 @@ class Immobilie
|
||||
public function setKaufpreis(?int $kaufpreis): self
|
||||
{
|
||||
$this->kaufpreis = $kaufpreis;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -295,11 +310,12 @@ class Immobilie
|
||||
public function setVerwalter(User $verwalter): self
|
||||
{
|
||||
$this->verwalter = $verwalter;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Berechnet die Gesamtfläche (Wohnfläche + Nutzfläche)
|
||||
* Berechnet die Gesamtfläche (Wohnfläche + Nutzfläche).
|
||||
*/
|
||||
public function getGesamtflaeche(): int
|
||||
{
|
||||
@@ -308,16 +324,16 @@ class Immobilie
|
||||
|
||||
/**
|
||||
* Berechnet die Kaufnebenkosten basierend auf dem Bundesland
|
||||
* Rückgabe: Array mit Notar, Grundbuch, Grunderwerbsteuer und Gesamt
|
||||
* Rückgabe: Array mit Notar, Grundbuch, Grunderwerbsteuer und Gesamt.
|
||||
*/
|
||||
public function getKaufnebenkosten(): array
|
||||
{
|
||||
if (!$this->getKaufpreis() || !$this->bundesland) {
|
||||
if (! $this->getKaufpreis() || ! $this->bundesland) {
|
||||
return [
|
||||
'notar' => 0,
|
||||
'grundbuch' => 0,
|
||||
'grunderwerbsteuer' => 0,
|
||||
'gesamt' => 0
|
||||
'gesamt' => 0,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -337,7 +353,7 @@ class Immobilie
|
||||
'notar' => $notar,
|
||||
'grundbuch' => $grundbuch,
|
||||
'grunderwerbsteuer' => $grunderwerbsteuer,
|
||||
'gesamt' => $gesamt
|
||||
'gesamt' => $gesamt,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
namespace App\Entity;
|
||||
|
||||
use ApiPlatform\Metadata\ApiResource;
|
||||
use ApiPlatform\Metadata\Delete;
|
||||
use ApiPlatform\Metadata\Get;
|
||||
use ApiPlatform\Metadata\GetCollection;
|
||||
use ApiPlatform\Metadata\Post;
|
||||
use ApiPlatform\Metadata\Put;
|
||||
use ApiPlatform\Metadata\Delete;
|
||||
use App\Enum\UserRole;
|
||||
use App\Repository\UserRepository;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
@@ -24,7 +24,7 @@ use Symfony\Component\Validator\Constraints as Assert;
|
||||
new GetCollection(),
|
||||
new Post(),
|
||||
new Put(),
|
||||
new Delete()
|
||||
new Delete(),
|
||||
]
|
||||
)]
|
||||
class User implements UserInterface
|
||||
@@ -65,11 +65,11 @@ class User implements UserInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* Generiert einen eindeutigen API-Key
|
||||
* Generiert einen eindeutigen API-Key.
|
||||
*/
|
||||
private function generateApiKey(): string
|
||||
{
|
||||
return hash('sha256', random_bytes(32) . microtime(true));
|
||||
return hash('sha256', random_bytes(32).microtime(true));
|
||||
}
|
||||
|
||||
public function getId(): ?int
|
||||
@@ -85,6 +85,7 @@ class User implements UserInterface
|
||||
public function setName(string $name): self
|
||||
{
|
||||
$this->name = $name;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -96,6 +97,7 @@ class User implements UserInterface
|
||||
public function setEmail(string $email): self
|
||||
{
|
||||
$this->email = $email;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -107,6 +109,7 @@ class User implements UserInterface
|
||||
public function setRole(UserRole $role): self
|
||||
{
|
||||
$this->role = $role;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -118,6 +121,7 @@ class User implements UserInterface
|
||||
public function setCreatedAt(\DateTimeInterface $createdAt): self
|
||||
{
|
||||
$this->createdAt = $createdAt;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -131,7 +135,7 @@ class User implements UserInterface
|
||||
|
||||
public function addImmobilie(Immobilie $immobilie): self
|
||||
{
|
||||
if (!$this->immobilien->contains($immobilie)) {
|
||||
if (! $this->immobilien->contains($immobilie)) {
|
||||
$this->immobilien->add($immobilie);
|
||||
$immobilie->setVerwalter($this);
|
||||
}
|
||||
@@ -142,6 +146,7 @@ class User implements UserInterface
|
||||
public function removeImmobilie(Immobilie $immobilie): self
|
||||
{
|
||||
$this->immobilien->removeElement($immobilie);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -151,16 +156,17 @@ class User implements UserInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* Generiert einen neuen API-Key
|
||||
* Generiert einen neuen API-Key.
|
||||
*/
|
||||
public function regenerateApiKey(): self
|
||||
{
|
||||
$this->apiKey = $this->generateApiKey();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* UserInterface Methods
|
||||
* UserInterface Methods.
|
||||
*/
|
||||
public function getUserIdentifier(): string
|
||||
{
|
||||
@@ -171,11 +177,11 @@ class User implements UserInterface
|
||||
{
|
||||
$roles = ['ROLE_USER'];
|
||||
|
||||
if ($this->role === UserRole::ADMIN) {
|
||||
if (UserRole::ADMIN === $this->role) {
|
||||
$roles[] = 'ROLE_ADMIN';
|
||||
} elseif ($this->role === UserRole::MODERATOR) {
|
||||
} elseif (UserRole::MODERATOR === $this->role) {
|
||||
$roles[] = 'ROLE_MODERATOR';
|
||||
} elseif ($this->role === UserRole::TECHNICAL) {
|
||||
} elseif (UserRole::TECHNICAL === $this->role) {
|
||||
$roles[] = 'ROLE_TECHNICAL';
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ enum Bundesland: string
|
||||
|
||||
public function getLabel(): string
|
||||
{
|
||||
return match($this) {
|
||||
return match ($this) {
|
||||
self::BADEN_WUERTTEMBERG => 'Baden-Württemberg',
|
||||
self::BAYERN => 'Bayern',
|
||||
self::BERLIN => 'Berlin',
|
||||
@@ -45,11 +45,11 @@ enum Bundesland: string
|
||||
|
||||
/**
|
||||
* Gibt die Grunderwerbsteuer in Prozent für das Bundesland zurück
|
||||
* Stand: 2025
|
||||
* Stand: 2025.
|
||||
*/
|
||||
public function getGrunderwerbsteuer(): float
|
||||
{
|
||||
return match($this) {
|
||||
return match ($this) {
|
||||
self::BADEN_WUERTTEMBERG => 5.0,
|
||||
self::BAYERN => 3.5,
|
||||
self::BERLIN => 6.0,
|
||||
|
||||
@@ -10,7 +10,7 @@ enum Heizungstyp: string
|
||||
|
||||
public function getLabel(): string
|
||||
{
|
||||
return match($this) {
|
||||
return match ($this) {
|
||||
self::GASHEIZUNG => 'Gasheizung',
|
||||
self::WAERMEPUMPE => 'Wärmepumpe',
|
||||
self::OELHEIZUNG => 'Ölheizung',
|
||||
|
||||
@@ -12,7 +12,7 @@ enum ImmobilienTyp: string
|
||||
|
||||
public function getLabel(): string
|
||||
{
|
||||
return match($this) {
|
||||
return match ($this) {
|
||||
self::WOHNUNG => 'Wohnung',
|
||||
self::HAUS => 'Haus',
|
||||
self::GRUNDSTUECK => 'Grundstück',
|
||||
|
||||
@@ -11,7 +11,7 @@ enum UserRole: string
|
||||
|
||||
public function getLabel(): string
|
||||
{
|
||||
return match($this) {
|
||||
return match ($this) {
|
||||
self::USER => 'Benutzer',
|
||||
self::ADMIN => 'Administrator',
|
||||
self::MODERATOR => 'Moderator',
|
||||
|
||||
@@ -18,7 +18,7 @@ class ImmobilieRepository extends ServiceEntityRepository
|
||||
}
|
||||
|
||||
/**
|
||||
* Find available properties
|
||||
* Find available properties.
|
||||
*/
|
||||
public function findVerfuegbare(): array
|
||||
{
|
||||
@@ -31,7 +31,7 @@ class ImmobilieRepository extends ServiceEntityRepository
|
||||
}
|
||||
|
||||
/**
|
||||
* Find properties by type
|
||||
* Find properties by type.
|
||||
*/
|
||||
public function findByTyp(ImmobilienTyp $typ): array
|
||||
{
|
||||
@@ -44,7 +44,7 @@ class ImmobilieRepository extends ServiceEntityRepository
|
||||
}
|
||||
|
||||
/**
|
||||
* Find properties within price range
|
||||
* Find properties within price range.
|
||||
*/
|
||||
public function findByPreisRange(float $minPreis, float $maxPreis): array
|
||||
{
|
||||
@@ -60,7 +60,7 @@ class ImmobilieRepository extends ServiceEntityRepository
|
||||
}
|
||||
|
||||
/**
|
||||
* Find properties within area range
|
||||
* Find properties within area range.
|
||||
*/
|
||||
public function findByFlaecheRange(float $minFlaeche, float $maxFlaeche): array
|
||||
{
|
||||
@@ -76,7 +76,7 @@ class ImmobilieRepository extends ServiceEntityRepository
|
||||
}
|
||||
|
||||
/**
|
||||
* Find properties with garage
|
||||
* Find properties with garage.
|
||||
*/
|
||||
public function findMitGarage(): array
|
||||
{
|
||||
@@ -91,7 +91,7 @@ class ImmobilieRepository extends ServiceEntityRepository
|
||||
}
|
||||
|
||||
/**
|
||||
* Find properties by minimum number of rooms
|
||||
* Find properties by minimum number of rooms.
|
||||
*/
|
||||
public function findByMinZimmer(int $minZimmer): array
|
||||
{
|
||||
@@ -106,7 +106,7 @@ class ImmobilieRepository extends ServiceEntityRepository
|
||||
}
|
||||
|
||||
/**
|
||||
* Get average price per sqm by type
|
||||
* Get average price per sqm by type.
|
||||
*/
|
||||
public function getAveragePreisProQmByTyp(ImmobilienTyp $typ): float
|
||||
{
|
||||
@@ -123,13 +123,13 @@ class ImmobilieRepository extends ServiceEntityRepository
|
||||
}
|
||||
|
||||
/**
|
||||
* Search properties by address
|
||||
* Search properties by address.
|
||||
*/
|
||||
public function searchByAdresse(string $search): array
|
||||
{
|
||||
return $this->createQueryBuilder('i')
|
||||
->andWhere('i.adresse LIKE :search')
|
||||
->setParameter('search', '%' . $search . '%')
|
||||
->setParameter('search', '%'.$search.'%')
|
||||
->orderBy('i.createdAt', 'DESC')
|
||||
->getQuery()
|
||||
->getResult();
|
||||
|
||||
@@ -17,7 +17,7 @@ class UserRepository extends ServiceEntityRepository
|
||||
}
|
||||
|
||||
/**
|
||||
* Find users by role
|
||||
* Find users by role.
|
||||
*/
|
||||
public function findByRole(string $role): array
|
||||
{
|
||||
@@ -30,7 +30,7 @@ class UserRepository extends ServiceEntityRepository
|
||||
}
|
||||
|
||||
/**
|
||||
* Find user by email
|
||||
* Find user by email.
|
||||
*/
|
||||
public function findOneByEmail(string $email): ?User
|
||||
{
|
||||
|
||||
@@ -35,10 +35,10 @@ class ApiKeyAuthenticator extends AbstractAuthenticator
|
||||
}
|
||||
|
||||
return new SelfValidatingPassport(
|
||||
new UserBadge($apiKey, function($apiKey) {
|
||||
new UserBadge($apiKey, function ($apiKey) {
|
||||
$user = $this->userRepository->findOneBy(['apiKey' => $apiKey]);
|
||||
|
||||
if (!$user) {
|
||||
if (! $user) {
|
||||
throw new CustomUserMessageAuthenticationException('Invalid API key');
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ class ApiKeyAuthenticator extends AbstractAuthenticator
|
||||
public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response
|
||||
{
|
||||
return new JsonResponse([
|
||||
'message' => strtr($exception->getMessageKey(), $exception->getMessageData())
|
||||
'message' => strtr($exception->getMessageKey(), $exception->getMessageData()),
|
||||
], Response::HTTP_UNAUTHORIZED);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,9 +10,9 @@ use Symfony\Component\Security\Core\Authorization\Voter\Voter;
|
||||
|
||||
class ImmobilieVoter extends Voter
|
||||
{
|
||||
const VIEW = 'view';
|
||||
const EDIT = 'edit';
|
||||
const DELETE = 'delete';
|
||||
public const VIEW = 'view';
|
||||
public const EDIT = 'edit';
|
||||
public const DELETE = 'delete';
|
||||
|
||||
protected function supports(string $attribute, mixed $subject): bool
|
||||
{
|
||||
@@ -26,7 +26,7 @@ class ImmobilieVoter extends Voter
|
||||
$user = $token->getUser();
|
||||
|
||||
// User muss eingeloggt sein
|
||||
if (!$user instanceof User) {
|
||||
if (! $user instanceof User) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -34,12 +34,12 @@ class ImmobilieVoter extends Voter
|
||||
$immobilie = $subject;
|
||||
|
||||
// Admin hat uneingeschränkten Zugriff
|
||||
if ($user->getRole() === UserRole::ADMIN) {
|
||||
if (UserRole::ADMIN === $user->getRole()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Prüfe je nach Attribut
|
||||
return match($attribute) {
|
||||
return match ($attribute) {
|
||||
self::VIEW => $this->canView($immobilie, $user),
|
||||
self::EDIT => $this->canEdit($immobilie, $user),
|
||||
self::DELETE => $this->canDelete($immobilie, $user),
|
||||
|
||||
27
symfony.lock
27
symfony.lock
@@ -49,6 +49,18 @@
|
||||
"./migrations/.gitignore"
|
||||
]
|
||||
},
|
||||
"friendsofphp/php-cs-fixer": {
|
||||
"version": "3.89",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "3.0",
|
||||
"ref": "be2103eb4a20942e28a6dd87736669b757132435"
|
||||
},
|
||||
"files": [
|
||||
".php-cs-fixer.dist.php"
|
||||
]
|
||||
},
|
||||
"nelmio/cors-bundle": {
|
||||
"version": "2.6",
|
||||
"recipe": {
|
||||
@@ -61,6 +73,21 @@
|
||||
"./config/packages/nelmio_cors.yaml"
|
||||
]
|
||||
},
|
||||
"phpunit/phpunit": {
|
||||
"version": "12.4",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "11.1",
|
||||
"ref": "1117deb12541f35793eec9fff7494d7aa12283fc"
|
||||
},
|
||||
"files": [
|
||||
".env.test",
|
||||
"phpunit.dist.xml",
|
||||
"tests/bootstrap.php",
|
||||
"bin/phpunit"
|
||||
]
|
||||
},
|
||||
"symfony/console": {
|
||||
"version": "7.3",
|
||||
"recipe": {
|
||||
|
||||
28
tests/Api/ApiDocumentationTest.php
Normal file
28
tests/Api/ApiDocumentationTest.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace App\Tests\Api;
|
||||
|
||||
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
|
||||
|
||||
class ApiDocumentationTest extends WebTestCase
|
||||
{
|
||||
public function testSwaggerUIAccessible(): void
|
||||
{
|
||||
$client = static::createClient();
|
||||
|
||||
// Test: Swagger UI ist öffentlich zugänglich
|
||||
$client->request('GET', '/api/docs.html');
|
||||
|
||||
$this->assertResponseIsSuccessful();
|
||||
}
|
||||
|
||||
public function testOpenAPIJsonLdAccessible(): void
|
||||
{
|
||||
$client = static::createClient();
|
||||
|
||||
// Test: OpenAPI JSON-LD ist öffentlich zugänglich
|
||||
$client->request('GET', '/api/docs.jsonld');
|
||||
|
||||
$this->assertResponseIsSuccessful();
|
||||
}
|
||||
}
|
||||
50
tests/Api/BundeslandApiTest.php
Normal file
50
tests/Api/BundeslandApiTest.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace App\Tests\Api;
|
||||
|
||||
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
|
||||
|
||||
class BundeslandApiTest extends WebTestCase
|
||||
{
|
||||
public function testGetBundeslaenderPublicAccess(): void
|
||||
{
|
||||
$client = static::createClient();
|
||||
|
||||
// Test: Bundesländer können ohne API-Key abgerufen werden
|
||||
$client->request('GET', '/api/bundeslands');
|
||||
|
||||
$this->assertResponseIsSuccessful();
|
||||
$this->assertResponseHeaderSame('content-type', 'application/ld+json; charset=utf-8');
|
||||
}
|
||||
|
||||
public function testGetSingleBundeslandPublicAccess(): void
|
||||
{
|
||||
$client = static::createClient();
|
||||
|
||||
// Test: Einzelnes Bundesland kann ohne API-Key abgerufen werden
|
||||
$client->request('GET', '/api/bundeslands/1');
|
||||
|
||||
// Response kann 200 (OK) oder 404 (Not Found) sein, beides ist akzeptabel
|
||||
$this->assertResponseStatusCodeSame(200);
|
||||
}
|
||||
|
||||
public function testCreateBundeslandRequiresAuthentication(): void
|
||||
{
|
||||
$client = static::createClient();
|
||||
|
||||
// Test: Bundesland erstellen ohne API-Key sollte fehlschlagen
|
||||
$client->request(
|
||||
'POST',
|
||||
'/api/bundeslands',
|
||||
[],
|
||||
[],
|
||||
['CONTENT_TYPE' => 'application/json'],
|
||||
json_encode([
|
||||
'name' => 'Test Bundesland',
|
||||
'grunderwerbsteuer' => 5.0,
|
||||
])
|
||||
);
|
||||
|
||||
$this->assertResponseStatusCodeSame(403); // Access Denied (no authentication on this firewall)
|
||||
}
|
||||
}
|
||||
49
tests/Api/HeizungstypApiTest.php
Normal file
49
tests/Api/HeizungstypApiTest.php
Normal file
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace App\Tests\Api;
|
||||
|
||||
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
|
||||
|
||||
class HeizungstypApiTest extends WebTestCase
|
||||
{
|
||||
public function testGetHeizungstypenPublicAccess(): void
|
||||
{
|
||||
$client = static::createClient();
|
||||
|
||||
// Test: Heizungstypen können ohne API-Key abgerufen werden
|
||||
$client->request('GET', '/api/heizungstyps');
|
||||
|
||||
$this->assertResponseIsSuccessful();
|
||||
$this->assertResponseHeaderSame('content-type', 'application/ld+json; charset=utf-8');
|
||||
}
|
||||
|
||||
public function testGetSingleHeizungstypPublicAccess(): void
|
||||
{
|
||||
$client = static::createClient();
|
||||
|
||||
// Test: Einzelner Heizungstyp kann ohne API-Key abgerufen werden
|
||||
$client->request('GET', '/api/heizungstyps/1');
|
||||
|
||||
// Response kann 200 (OK) oder 404 (Not Found) sein
|
||||
$this->assertResponseStatusCodeSame(200);
|
||||
}
|
||||
|
||||
public function testCreateHeizungstypRequiresAuthentication(): void
|
||||
{
|
||||
$client = static::createClient();
|
||||
|
||||
// Test: Heizungstyp erstellen ohne API-Key sollte fehlschlagen
|
||||
$client->request(
|
||||
'POST',
|
||||
'/api/heizungstyps',
|
||||
[],
|
||||
[],
|
||||
['CONTENT_TYPE' => 'application/json'],
|
||||
json_encode([
|
||||
'name' => 'Test Heizung',
|
||||
])
|
||||
);
|
||||
|
||||
$this->assertResponseStatusCodeSame(401); // Unauthorized
|
||||
}
|
||||
}
|
||||
65
tests/Entity/BundeslandTest.php
Normal file
65
tests/Entity/BundeslandTest.php
Normal file
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
namespace App\Tests\Entity;
|
||||
|
||||
use App\Entity\Bundesland;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class BundeslandTest extends TestCase
|
||||
{
|
||||
public function testBundeslandCreation(): void
|
||||
{
|
||||
$bundesland = new Bundesland();
|
||||
$bundesland->setName('Bayern');
|
||||
$bundesland->setGrunderwerbsteuer(3.5);
|
||||
|
||||
$this->assertEquals('Bayern', $bundesland->getName());
|
||||
$this->assertEquals(3.5, $bundesland->getGrunderwerbsteuer());
|
||||
}
|
||||
|
||||
public function testGrunderwerbsteuerValues(): void
|
||||
{
|
||||
$testCases = [
|
||||
['Baden-Württemberg', 5.0],
|
||||
['Bayern', 3.5],
|
||||
['Berlin', 6.0],
|
||||
['Brandenburg', 6.5],
|
||||
['Bremen', 5.0],
|
||||
['Hamburg', 5.5],
|
||||
['Hessen', 6.0],
|
||||
['Mecklenburg-Vorpommern', 6.0],
|
||||
['Niedersachsen', 5.0],
|
||||
['Nordrhein-Westfalen', 6.5],
|
||||
['Rheinland-Pfalz', 5.0],
|
||||
['Saarland', 6.5],
|
||||
['Sachsen', 5.5],
|
||||
['Sachsen-Anhalt', 5.0],
|
||||
['Schleswig-Holstein', 6.5],
|
||||
['Thüringen', 5.0],
|
||||
];
|
||||
|
||||
foreach ($testCases as [$name, $steuer]) {
|
||||
$bundesland = new Bundesland();
|
||||
$bundesland->setName($name);
|
||||
$bundesland->setGrunderwerbsteuer($steuer);
|
||||
|
||||
$this->assertEquals($name, $bundesland->getName());
|
||||
$this->assertEquals($steuer, $bundesland->getGrunderwerbsteuer());
|
||||
}
|
||||
}
|
||||
|
||||
public function testMinMaxGrunderwerbsteuer(): void
|
||||
{
|
||||
// Niedrigster Satz: Bayern mit 3.5%
|
||||
$bayern = new Bundesland();
|
||||
$bayern->setName('Bayern');
|
||||
$bayern->setGrunderwerbsteuer(3.5);
|
||||
$this->assertEquals(3.5, $bayern->getGrunderwerbsteuer());
|
||||
|
||||
// Höchster Satz: Brandenburg, NRW, Saarland, SH mit 6.5%
|
||||
$nrw = new Bundesland();
|
||||
$nrw->setName('Nordrhein-Westfalen');
|
||||
$nrw->setGrunderwerbsteuer(6.5);
|
||||
$this->assertEquals(6.5, $nrw->getGrunderwerbsteuer());
|
||||
}
|
||||
}
|
||||
29
tests/Entity/HeizungstypTest.php
Normal file
29
tests/Entity/HeizungstypTest.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\Tests\Entity;
|
||||
|
||||
use App\Entity\Heizungstyp;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class HeizungstypTest extends TestCase
|
||||
{
|
||||
public function testHeizungstypCreation(): void
|
||||
{
|
||||
$heizungstyp = new Heizungstyp();
|
||||
$heizungstyp->setName('Wärmepumpe');
|
||||
|
||||
$this->assertEquals('Wärmepumpe', $heizungstyp->getName());
|
||||
}
|
||||
|
||||
public function testCommonHeizungstypen(): void
|
||||
{
|
||||
$typen = ['Gasheizung', 'Wärmepumpe', 'Ölheizung', 'Fernwärme', 'Pelletheizung'];
|
||||
|
||||
foreach ($typen as $typName) {
|
||||
$heizungstyp = new Heizungstyp();
|
||||
$heizungstyp->setName($typName);
|
||||
|
||||
$this->assertEquals($typName, $heizungstyp->getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
160
tests/Entity/ImmobilieTest.php
Normal file
160
tests/Entity/ImmobilieTest.php
Normal file
@@ -0,0 +1,160 @@
|
||||
<?php
|
||||
|
||||
namespace App\Tests\Entity;
|
||||
|
||||
use App\Entity\Bundesland;
|
||||
use App\Entity\Immobilie;
|
||||
use App\Entity\User;
|
||||
use App\Enum\ImmobilienTyp;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class ImmobilieTest extends TestCase
|
||||
{
|
||||
private User $verwalter;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->verwalter = new User();
|
||||
$this->verwalter->setName('Test Verwalter');
|
||||
$this->verwalter->setEmail('verwalter@example.com');
|
||||
}
|
||||
|
||||
public function testImmobilieCreation(): void
|
||||
{
|
||||
$immobilie = new Immobilie();
|
||||
$immobilie->setVerwalter($this->verwalter);
|
||||
$immobilie->setAdresse('Teststraße 123, 12345 Teststadt');
|
||||
$immobilie->setWohnflaeche(100);
|
||||
$immobilie->setNutzflaeche(20);
|
||||
$immobilie->setZimmer(4);
|
||||
$immobilie->setTyp(ImmobilienTyp::WOHNUNG);
|
||||
|
||||
$this->assertEquals('Teststraße 123, 12345 Teststadt', $immobilie->getAdresse());
|
||||
$this->assertEquals(100, $immobilie->getWohnflaeche());
|
||||
$this->assertEquals(20, $immobilie->getNutzflaeche());
|
||||
$this->assertEquals(4, $immobilie->getZimmer());
|
||||
$this->assertEquals(ImmobilienTyp::WOHNUNG, $immobilie->getTyp());
|
||||
$this->assertEquals($this->verwalter, $immobilie->getVerwalter());
|
||||
}
|
||||
|
||||
public function testGesamtflaecheCalculation(): void
|
||||
{
|
||||
$immobilie = new Immobilie();
|
||||
$immobilie->setVerwalter($this->verwalter);
|
||||
$immobilie->setAdresse('Test');
|
||||
$immobilie->setWohnflaeche(85);
|
||||
$immobilie->setNutzflaeche(15);
|
||||
$immobilie->setZimmer(3);
|
||||
|
||||
$this->assertEquals(100, $immobilie->getGesamtflaeche());
|
||||
}
|
||||
|
||||
public function testKaufnebenkostenWithoutBundesland(): void
|
||||
{
|
||||
$immobilie = new Immobilie();
|
||||
$immobilie->setVerwalter($this->verwalter);
|
||||
$immobilie->setAdresse('Test');
|
||||
$immobilie->setWohnflaeche(100);
|
||||
$immobilie->setNutzflaeche(0);
|
||||
$immobilie->setZimmer(3);
|
||||
$immobilie->setKaufpreis(300000);
|
||||
|
||||
$kosten = $immobilie->getKaufnebenkosten();
|
||||
|
||||
$this->assertEquals(0, $kosten['notar']);
|
||||
$this->assertEquals(0, $kosten['grundbuch']);
|
||||
$this->assertEquals(0, $kosten['grunderwerbsteuer']);
|
||||
$this->assertEquals(0, $kosten['gesamt']);
|
||||
}
|
||||
|
||||
public function testKaufnebenkostenWithBundesland(): void
|
||||
{
|
||||
$bundesland = new Bundesland();
|
||||
$bundesland->setName('Bayern');
|
||||
$bundesland->setGrunderwerbsteuer(3.5);
|
||||
|
||||
$immobilie = new Immobilie();
|
||||
$immobilie->setVerwalter($this->verwalter);
|
||||
$immobilie->setAdresse('Test');
|
||||
$immobilie->setWohnflaeche(100);
|
||||
$immobilie->setNutzflaeche(0);
|
||||
$immobilie->setZimmer(3);
|
||||
$immobilie->setKaufpreis(300000);
|
||||
$immobilie->setBundesland($bundesland);
|
||||
|
||||
$kosten = $immobilie->getKaufnebenkosten();
|
||||
|
||||
// Notar: 1.5% von 300000 = 4500
|
||||
$this->assertEquals(4500, $kosten['notar']);
|
||||
|
||||
// Grundbuch: 0.5% von 300000 = 1500
|
||||
$this->assertEquals(1500, $kosten['grundbuch']);
|
||||
|
||||
// Grunderwerbsteuer: 3.5% von 300000 = 10500
|
||||
$this->assertEqualsWithDelta(10500, $kosten['grunderwerbsteuer'], 0.01);
|
||||
|
||||
// Gesamt: 4500 + 1500 + 10500 = 16500
|
||||
$this->assertEquals(16500, $kosten['gesamt']);
|
||||
}
|
||||
|
||||
public function testKaufnebenkostenWithDifferentBundesland(): void
|
||||
{
|
||||
$bundesland = new Bundesland();
|
||||
$bundesland->setName('Nordrhein-Westfalen');
|
||||
$bundesland->setGrunderwerbsteuer(6.5);
|
||||
|
||||
$immobilie = new Immobilie();
|
||||
$immobilie->setVerwalter($this->verwalter);
|
||||
$immobilie->setAdresse('Test');
|
||||
$immobilie->setWohnflaeche(100);
|
||||
$immobilie->setNutzflaeche(0);
|
||||
$immobilie->setZimmer(3);
|
||||
$immobilie->setKaufpreis(400000);
|
||||
$immobilie->setBundesland($bundesland);
|
||||
|
||||
$kosten = $immobilie->getKaufnebenkosten();
|
||||
|
||||
// Notar: 1.5% von 400000 = 6000
|
||||
$this->assertEquals(6000, $kosten['notar']);
|
||||
|
||||
// Grundbuch: 0.5% von 400000 = 2000
|
||||
$this->assertEquals(2000, $kosten['grundbuch']);
|
||||
|
||||
// Grunderwerbsteuer: 6.5% von 400000 = 26000
|
||||
$this->assertEquals(26000, $kosten['grunderwerbsteuer']);
|
||||
|
||||
// Gesamt: 6000 + 2000 + 26000 = 34000
|
||||
$this->assertEquals(34000, $kosten['gesamt']);
|
||||
}
|
||||
|
||||
public function testDefaultValues(): void
|
||||
{
|
||||
$immobilie = new Immobilie();
|
||||
|
||||
$this->assertEquals(ImmobilienTyp::WOHNUNG, $immobilie->getTyp());
|
||||
$this->assertEquals(0, $immobilie->getNutzflaeche());
|
||||
$this->assertFalse($immobilie->getGarage());
|
||||
$this->assertNotNull($immobilie->getCreatedAt());
|
||||
$this->assertNotNull($immobilie->getUpdatedAt());
|
||||
}
|
||||
|
||||
public function testOptionalFields(): void
|
||||
{
|
||||
$immobilie = new Immobilie();
|
||||
$immobilie->setVerwalter($this->verwalter);
|
||||
$immobilie->setAdresse('Test');
|
||||
$immobilie->setWohnflaeche(100);
|
||||
$immobilie->setNutzflaeche(0);
|
||||
$immobilie->setZimmer(3);
|
||||
|
||||
$immobilie->setBaujahr(2020);
|
||||
$immobilie->setEtage(3);
|
||||
$immobilie->setAbschreibungszeit(50);
|
||||
$immobilie->setBeschreibung('Schöne Wohnung');
|
||||
|
||||
$this->assertEquals(2020, $immobilie->getBaujahr());
|
||||
$this->assertEquals(3, $immobilie->getEtage());
|
||||
$this->assertEquals(50, $immobilie->getAbschreibungszeit());
|
||||
$this->assertEquals('Schöne Wohnung', $immobilie->getBeschreibung());
|
||||
}
|
||||
}
|
||||
92
tests/Entity/UserTest.php
Normal file
92
tests/Entity/UserTest.php
Normal file
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
|
||||
namespace App\Tests\Entity;
|
||||
|
||||
use App\Entity\User;
|
||||
use App\Enum\UserRole;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class UserTest extends TestCase
|
||||
{
|
||||
public function testUserCreation(): void
|
||||
{
|
||||
$user = new User();
|
||||
$user->setName('Max Mustermann');
|
||||
$user->setEmail('max@example.com');
|
||||
$user->setRole(UserRole::USER);
|
||||
|
||||
$this->assertEquals('Max Mustermann', $user->getName());
|
||||
$this->assertEquals('max@example.com', $user->getEmail());
|
||||
$this->assertEquals(UserRole::USER, $user->getRole());
|
||||
$this->assertNotNull($user->getApiKey());
|
||||
$this->assertNotNull($user->getCreatedAt());
|
||||
}
|
||||
|
||||
public function testApiKeyGeneration(): void
|
||||
{
|
||||
$user = new User();
|
||||
$user->setName('Test User');
|
||||
$user->setEmail('test@example.com');
|
||||
|
||||
$apiKey = $user->getApiKey();
|
||||
|
||||
$this->assertNotEmpty($apiKey);
|
||||
$this->assertEquals(64, strlen($apiKey)); // SHA256 hash length
|
||||
}
|
||||
|
||||
public function testRegenerateApiKey(): void
|
||||
{
|
||||
$user = new User();
|
||||
$user->setName('Test User');
|
||||
$user->setEmail('test@example.com');
|
||||
|
||||
$originalKey = $user->getApiKey();
|
||||
$user->regenerateApiKey();
|
||||
$newKey = $user->getApiKey();
|
||||
|
||||
$this->assertNotEquals($originalKey, $newKey);
|
||||
$this->assertEquals(64, strlen($newKey));
|
||||
}
|
||||
|
||||
public function testUserRoles(): void
|
||||
{
|
||||
// Test USER role
|
||||
$user = new User();
|
||||
$user->setRole(UserRole::USER);
|
||||
$this->assertContains('ROLE_USER', $user->getRoles());
|
||||
|
||||
// Test ADMIN role
|
||||
$admin = new User();
|
||||
$admin->setRole(UserRole::ADMIN);
|
||||
$this->assertContains('ROLE_ADMIN', $admin->getRoles());
|
||||
$this->assertContains('ROLE_USER', $admin->getRoles());
|
||||
|
||||
// Test TECHNICAL role
|
||||
$technical = new User();
|
||||
$technical->setRole(UserRole::TECHNICAL);
|
||||
$this->assertContains('ROLE_TECHNICAL', $technical->getRoles());
|
||||
$this->assertContains('ROLE_USER', $technical->getRoles());
|
||||
|
||||
// Test MODERATOR role
|
||||
$moderator = new User();
|
||||
$moderator->setRole(UserRole::MODERATOR);
|
||||
$this->assertContains('ROLE_MODERATOR', $moderator->getRoles());
|
||||
$this->assertContains('ROLE_USER', $moderator->getRoles());
|
||||
}
|
||||
|
||||
public function testUserIdentifier(): void
|
||||
{
|
||||
$user = new User();
|
||||
$user->setEmail('identifier@example.com');
|
||||
|
||||
$this->assertEquals('identifier@example.com', $user->getUserIdentifier());
|
||||
}
|
||||
|
||||
public function testDefaultRole(): void
|
||||
{
|
||||
$user = new User();
|
||||
|
||||
// Default role should be USER
|
||||
$this->assertEquals(UserRole::USER, $user->getRole());
|
||||
}
|
||||
}
|
||||
13
tests/bootstrap.php
Normal file
13
tests/bootstrap.php
Normal file
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
use Symfony\Component\Dotenv\Dotenv;
|
||||
|
||||
require dirname(__DIR__).'/vendor/autoload.php';
|
||||
|
||||
if (method_exists(Dotenv::class, 'bootEnv')) {
|
||||
(new Dotenv())->bootEnv(dirname(__DIR__).'/.env');
|
||||
}
|
||||
|
||||
if ($_SERVER['APP_DEBUG']) {
|
||||
umask(0000);
|
||||
}
|
||||
Reference in New Issue
Block a user