Add auth test suite, API tests and database migration tests

This commit is contained in:
Yanis
2026-05-27 16:27:42 +02:00
parent deba81fadb
commit 886c204fa5
10 changed files with 1792 additions and 2 deletions

View File

@@ -0,0 +1,198 @@
<?php
use CodeIgniter\Test\CIUnitTestCase;
use CodeIgniter\Test\DatabaseTestTrait;
/**
* MigrationTest - Tests für Datenbankmigrationen
* Verifiziert dass alle Migrationen korrekt ausgeführt werden
* und die Tabellen mit korrekten Spalten erstellt werden
*
* @internal
*/
final class MigrationTest extends CIUnitTestCase
{
use DatabaseTestTrait;
/**
* Test: Users Tabelle existiert
*/
public function testUsersTableExists(): void
{
$db = \Config\Database::connect();
$this->assertTrue($db->tableExists('users'));
}
/**
* Test: Users Tabelle hat erforderliche Spalten
*/
public function testUsersTableHasRequiredColumns(): void
{
$db = \Config\Database::connect();
$fields = $db->getFieldData('users');
$fieldNames = array_map(function ($field) {
return $field->name;
}, $fields);
$this->assertContains('id', $fieldNames);
$this->assertContains('email', $fieldNames);
$this->assertContains('password_hash', $fieldNames);
$this->assertContains('name', $fieldNames);
$this->assertContains('avatar_url', $fieldNames);
$this->assertContains('settings', $fieldNames);
$this->assertContains('created_at', $fieldNames);
$this->assertContains('updated_at', $fieldNames);
}
/**
* Test: Email Spalte ist unique
*/
public function testEmailIsUnique(): void
{
$db = \Config\Database::connect();
$builder = $db->table('users');
// Insert erstes Datensatz
$builder->insert([
'id' => 'unique-test-1',
'email' => 'unique@example.com',
'password_hash' => 'hash1',
'name' => 'Test One',
]);
// Versuche zweites Datensatz mit gleicher Email zu inserten
try {
$builder->insert([
'id' => 'unique-test-2',
'email' => 'unique@example.com',
'password_hash' => 'hash2',
'name' => 'Test Two',
]);
// Falls kein Error, gibt es ein Problem
$this->fail('Unique constraint wurde nicht erzwungen');
} catch (\Exception $e) {
// Expected - unique constraint wurde erzwungen
$this->assertTrue(true);
}
}
/**
* Test: Categories Tabelle existiert
*/
public function testCategoriesTableExists(): void
{
$db = \Config\Database::connect();
$this->assertTrue($db->tableExists('categories'));
}
/**
* Test: Projects Tabelle existiert
*/
public function testProjectsTableExists(): void
{
$db = \Config\Database::connect();
$this->assertTrue($db->tableExists('projects'));
}
/**
* Test: Todos Tabelle existiert
*/
public function testTodosTableExists(): void
{
$db = \Config\Database::connect();
$this->assertTrue($db->tableExists('todos'));
}
/**
* Test: TodoCategories Tabelle existiert
*/
public function testTodoCategoriesTableExists(): void
{
$db = \Config\Database::connect();
$this->assertTrue($db->tableExists('todo_categories'));
}
/**
* Test: Todos Tabelle hat erforderliche Spalten
*/
public function testTodosTableHasRequiredColumns(): void
{
$db = \Config\Database::connect();
$fields = $db->getFieldData('todos');
$fieldNames = array_map(function ($field) {
return $field->name;
}, $fields);
// Diese Spalten sollten mindestens existieren
$this->assertContains('id', $fieldNames);
// Weitere Standard-Spalten...
}
/**
* Test: Datenbank Verbindung funktioniert
*/
public function testDatabaseConnectionWorks(): void
{
$db = \Config\Database::connect();
$this->assertNotNull($db);
}
/**
* Test: Schema wird nicht über Migration hinaus modifiziert
*/
public function testTableCountIsCorrect(): void
{
$db = \Config\Database::connect();
// Abrufen aller Tabellen
$tables = $db->listTables();
// Sollte mindestens diese Tabellen haben
$requiredTables = ['users', 'categories', 'projects', 'todos', 'todo_categories'];
foreach ($requiredTables as $table) {
$this->assertContains($table, $tables, "Tabelle '{$table}' existiert nicht");
}
}
/**
* Test: Users settings Spalte ist JSON
*/
public function testUserSettingsIsJson(): void
{
$db = \Config\Database::connect();
$fields = $db->getFieldData('users');
$settingsField = null;
foreach ($fields as $field) {
if ($field->name === 'settings') {
$settingsField = $field;
break;
}
}
$this->assertNotNull($settingsField);
// Type sollte JSON-ähnlich sein
$this->assertStringContainsString('json', strtolower($settingsField->type));
}
/**
* Test: Timestamps sind in correct format
*/
public function testTimestampsAreCorrectType(): void
{
$db = \Config\Database::connect();
$fields = $db->getFieldData('users');
$dateFields = [];
foreach ($fields as $field) {
if (in_array($field->name, ['created_at', 'updated_at'])) {
$dateFields[] = $field;
}
}
$this->assertCount(2, $dateFields);
}
}

View File

@@ -0,0 +1,222 @@
<?php
namespace Tests\Feature;
use App\Models\UserModel;
use CodeIgniter\Test\CIUnitTestCase;
use CodeIgniter\Test\DatabaseTestTrait;
use CodeIgniter\Test\FeatureTestTrait;
/**
* AuthApiTest - Feature Tests für Auth API
* Testet die Authentication API Endpoints und HTTP Requests/Responses
*
* @internal
*/
final class AuthApiTest extends CIUnitTestCase
{
use DatabaseTestTrait;
use FeatureTestTrait;
protected $namespace = 'App\Controllers';
/**
* Test: Login API gibt 200 zurück für GET auf /auth/login
*/
public function testGetLoginPageReturns200(): void
{
$response = $this->get('/auth/login');
$this->assertTrue($response->getStatusCode() === 200);
$this->assertStringContainsString('form', (string)$response);
}
/**
* Test: Login API gibt 302 (Redirect) zurück mit gültigen Daten
*/
public function testLoginWithValidDataReturns302(): void
{
$userModel = new UserModel();
$userModel->insert([
'email' => 'api@example.com',
'password_hash' => password_hash('password123', PASSWORD_DEFAULT),
'name' => 'API Test',
]);
$response = $this->post('/auth/attemptLogin', [
'email' => 'api@example.com',
'password' => 'password123',
]);
$this->assertTrue($response->getStatusCode() === 302);
}
/**
* Test: Register API erstellt neuen Benutzer
*/
public function testRegisterApiCreatesNewUser(): void
{
$response = $this->post('/auth/attemptRegister', [
'name' => 'API User',
'email' => 'apiregister@example.com',
'password' => 'password123',
]);
$this->assertTrue($response->getStatusCode() === 302);
// Verifiziere dass Benutzer in Datenbank erstellt wurde
$userModel = new UserModel();
$user = $userModel->where('email', 'apiregister@example.com')->first();
$this->assertNotNull($user);
$this->assertEquals('API User', $user['name']);
}
/**
* Test: Login API mit falschen Credentials
*/
public function testLoginWithInvalidDataReturns302(): void
{
$response = $this->post('/auth/attemptLogin', [
'email' => 'nonexistent@api.com',
'password' => 'wrongpassword',
]);
// Sollte redirect sein (zur Login Seite zurück)
$this->assertTrue($response->getStatusCode() === 302);
}
/**
* Test: Logout API gibt 302 Redirect zurück
*/
public function testLogoutApiReturns302(): void
{
$response = $this->get('/auth/logout');
$this->assertTrue($response->getStatusCode() === 302);
}
/**
* Test: POST mit fehlenden Email Feld
*/
public function testLoginWithMissingEmailField(): void
{
$response = $this->post('/auth/attemptLogin', [
'password' => 'password123',
]);
// Sollte fehlschlagen
$this->assertTrue($response->getStatusCode() === 302);
}
/**
* Test: POST mit fehlenden Password Feld
*/
public function testLoginWithMissingPasswordField(): void
{
$response = $this->post('/auth/attemptLogin', [
'email' => 'test@example.com',
]);
// Sollte fehlschlagen
$this->assertTrue($response->getStatusCode() === 302);
}
/**
* Test: Register mit fehlenden Name Feld
*/
public function testRegisterWithMissingNameField(): void
{
$response = $this->post('/auth/attemptRegister', [
'email' => 'noname@example.com',
'password' => 'password123',
]);
// Sollte weiterleiten (möglicherweise mit Error)
$this->assertTrue($response->getStatusCode() === 302);
}
/**
* Test: Content-Type ist richtig bei erfolgreicher Login Seite
*/
public function testLoginPageContentType(): void
{
$response = $this->get('/auth/login');
$this->assertStringContainsString('text/html', $response->getHeaderLine('Content-Type'));
}
/**
* Test: Register API validiert Email Format
*/
public function testRegisterValidatesEmailFormat(): void
{
$response = $this->post('/auth/attemptRegister', [
'name' => 'Invalid Email',
'email' => 'not-an-email',
'password' => 'password123',
]);
// Sollte fehlschlagen oder Fehler zurückgeben
$this->assertTrue($response->getStatusCode() === 302);
}
/**
* Test: Login API Response Headers enthalten Sicherheits-Header
*/
public function testLoginPageIncludesSecurityHeaders(): void
{
$response = $this->get('/auth/login');
// Bootstrap und CSS sollten geladen sein
$content = (string)$response;
$this->assertStringContainsString('bootstrap', strtolower($content));
}
/**
* Test: Register API setzt Benutzer-ID in Session
*/
public function testRegisterSetsUserIdInSession(): void
{
$this->post('/auth/attemptRegister', [
'name' => 'Session Test',
'email' => 'session@api.com',
'password' => 'password123',
]);
// Benutzer sollte in DB existieren
$userModel = new UserModel();
$user = $userModel->where('email', 'session@api.com')->first();
$this->assertNotNull($user);
$this->assertNotNull($user['id']);
}
/**
* Test: Multiple Login Versuche
*/
public function testMultipleLoginAttempts(): void
{
$userModel = new UserModel();
$userModel->insert([
'email' => 'multi@example.com',
'password_hash' => password_hash('correct', PASSWORD_DEFAULT),
'name' => 'Multi Test',
]);
// Erster Versuch (falsch)
$response1 = $this->post('/auth/attemptLogin', [
'email' => 'multi@example.com',
'password' => 'wrong',
]);
// Zweiter Versuch (korrekt)
$response2 = $this->post('/auth/attemptLogin', [
'email' => 'multi@example.com',
'password' => 'correct',
]);
// Beide sollten 302 sein (redirect)
$this->assertTrue($response1->getStatusCode() === 302);
$this->assertTrue($response2->getStatusCode() === 302);
}
}

View File

@@ -0,0 +1,213 @@
<?php
namespace Tests\Unit\Controllers;
use App\Controllers\Auth;
use App\Models\UserModel;
use CodeIgniter\Test\CIUnitTestCase;
use CodeIgniter\Test\DatabaseTestTrait;
use CodeIgniter\Test\FeatureTestTrait;
/**
* AuthControllerTest - Unit Tests für den Auth Controller
* Testet Login, Registrierung und Logout Funktionalität
*
* @internal
*/
final class AuthControllerTest extends CIUnitTestCase
{
use DatabaseTestTrait;
use FeatureTestTrait;
protected $namespace = 'App\Controllers';
/**
* Test: Login Seite wird angezeigt
*/
public function testLoginPageLoads(): void
{
$response = $this->get('/auth/login');
$this->assertTrue($response->getStatusCode() === 200);
$this->assertStringContainsString('Todo App', (string)$response);
$this->assertStringContainsString('Anmelden', (string)$response);
}
/**
* Test: Login mit gültigen Credentials
*/
public function testLoginWithValidCredentials(): void
{
// Benutzer in der Datenbank erstellen
$userModel = new UserModel();
$userData = [
'email' => 'test@example.com',
'password_hash' => password_hash('password123', PASSWORD_DEFAULT),
'name' => 'Test User',
];
$userModel->insert($userData);
// POST Request zum Login
$response = $this->post('/auth/attemptLogin', [
'email' => 'test@example.com',
'password' => 'password123',
]);
// Sollte zu /dashboard weiterleiten
$this->assertTrue($response->getStatusCode() === 302);
}
/**
* Test: Login mit ungültigen Credentials
*/
public function testLoginWithInvalidCredentials(): void
{
$response = $this->post('/auth/attemptLogin', [
'email' => 'nonexistent@example.com',
'password' => 'wrongpassword',
]);
$this->assertTrue($response->getStatusCode() === 302);
}
/**
* Test: Registrierung mit gültigen Daten
*/
public function testRegisterWithValidData(): void
{
$response = $this->post('/auth/attemptRegister', [
'name' => 'Neuer User',
'email' => 'newuser@example.com',
'password' => 'password123',
]);
$this->assertTrue($response->getStatusCode() === 302);
$userModel = new UserModel();
$user = $userModel->where('email', 'newuser@example.com')->first();
$this->assertNotNull($user);
$this->assertEquals('Neuer User', $user['name']);
$this->assertEquals('newuser@example.com', $user['email']);
}
/**
* Test: Registrierung mit doppelter Email sollte fehlschlagen
*/
public function testRegisterWithDuplicateEmail(): void
{
$this->post('/auth/attemptRegister', [
'name' => 'User One',
'email' => 'duplicate@example.com',
'password' => 'password123',
]);
$response = $this->post('/auth/attemptRegister', [
'name' => 'User Two',
'email' => 'duplicate@example.com',
'password' => 'password456',
]);
$this->assertTrue($response->getStatusCode() === 302);
}
/**
* Test: Logout zerstört Session
*/
public function testLogout(): void
{
$userModel = new UserModel();
$userData = [
'email' => 'logout@example.com',
'password_hash' => password_hash('password123', PASSWORD_DEFAULT),
'name' => 'Logout Test User',
];
$userModel->insert($userData);
$this->post('/auth/attemptLogin', [
'email' => 'logout@example.com',
'password' => 'password123',
]);
$response = $this->get('/auth/logout');
$this->assertTrue($response->getStatusCode() === 302);
}
/**
* Test: Passwort wird korrekt gehasht
*/
public function testPasswordIsHashed(): void
{
$password = 'plaintext_password_123';
$response = $this->post('/auth/attemptRegister', [
'name' => 'Hash Test',
'email' => 'hash@example.com',
'password' => $password,
]);
$userModel = new UserModel();
$user = $userModel->where('email', 'hash@example.com')->first();
// Passwort sollte nicht im Klartext gespeichert sein
$this->assertNotEquals($password, $user['password_hash']);
// password_verify sollte true zurückgeben
$this->assertTrue(password_verify($password, $user['password_hash']));
}
/**
* Test: Email ist erforderlich beim Login
*/
public function testLoginRequiresEmail(): void
{
$response = $this->post('/auth/attemptLogin', [
'email' => '',
'password' => 'password123',
]);
$this->assertTrue($response->getStatusCode() === 302);
}
/**
* Test: Email ist erforderlich bei Registrierung
*/
public function testRegisterRequiresEmail(): void
{
$response = $this->post('/auth/attemptRegister', [
'name' => 'Test',
'email' => '',
'password' => 'password123',
]);
$this->assertTrue($response->getStatusCode() === 302);
}
/**
* Test: Login mit ungültiger Email-Adresse
*/
public function testLoginWithInvalidEmail(): void
{
$response = $this->post('/auth/attemptLogin', [
'email' => 'not-an-email',
'password' => 'password123',
]);
$this->assertTrue($response->getStatusCode() === 302);
}
/**
* Test: Session wird nach erfolgreicher Registrierung gesetzt
*/
public function testSessionIsSetAfterRegistration(): void
{
$response = $this->post('/auth/attemptRegister', [
'name' => 'Session Test',
'email' => 'session@example.com',
'password' => 'password123',
]);
$userModel = new UserModel();
$user = $userModel->where('email', 'session@example.com')->first();
$this->assertNotNull($user);
}
}

View File

@@ -0,0 +1,172 @@
<?php
namespace Tests\Unit\Models;
use App\Models\UserModel;
use CodeIgniter\Test\CIUnitTestCase;
use CodeIgniter\Test\DatabaseTestTrait;
/**
* UserModelTest - Unit Tests für das UserModel
* Testet die Benutzerdatenbankoperationen
*
* @internal
*/
final class UserModelTest extends CIUnitTestCase
{
use DatabaseTestTrait;
protected $namespace = 'App\Models';
/**
* Test: Benutzer kann erstellt werden
*/
public function testUserCanBeCreated(): void
{
$userModel = new UserModel();
$data = [
'email' => 'user@example.com',
'password_hash' => password_hash('password123', PASSWORD_DEFAULT),
'name' => 'Test User',
];
$id = $userModel->insert($data);
$this->assertIsNotNull($id);
}
/**
* Test: Benutzer kann nach Email gefunden werden
*/
public function testUserCanBeFoundByEmail(): void
{
$userModel = new UserModel();
$data = [
'email' => 'find@example.com',
'password_hash' => password_hash('password123', PASSWORD_DEFAULT),
'name' => 'Find User',
];
$userModel->insert($data);
$user = $userModel->where('email', 'find@example.com')->first();
$this->assertNotNull($user);
$this->assertEquals('find@example.com', $user['email']);
$this->assertEquals('Find User', $user['name']);
}
/**
* Test: Doppelte Email wird verhindert
*/
public function testDuplicateEmailIsRejected(): void
{
$userModel = new UserModel();
$data = [
'email' => 'duplicate@example.com',
'password_hash' => password_hash('password123', PASSWORD_DEFAULT),
'name' => 'First User',
];
$userModel->insert($data);
$duplicateData = [
'email' => 'duplicate@example.com',
'password_hash' => password_hash('password456', PASSWORD_DEFAULT),
'name' => 'Second User',
];
$result = $userModel->insert($duplicateData);
// Sollte false zurückgeben wegen Validierungsfehler
$this->assertFalse($result);
}
/**
* Test: Benutzer kann aktualisiert werden
*/
public function testUserCanBeUpdated(): void
{
$userModel = new UserModel();
$data = [
'email' => 'update@example.com',
'password_hash' => password_hash('password123', PASSWORD_DEFAULT),
'name' => 'Original Name',
];
$id = $userModel->insert($data);
$updateData = [
'name' => 'Updated Name',
];
$userModel->update($id, $updateData);
$updated = $userModel->find($id);
$this->assertEquals('Updated Name', $updated['name']);
}
/**
* Test: Benutzer kann gelöscht werden
*/
public function testUserCanBeDeleted(): void
{
$userModel = new UserModel();
$data = [
'email' => 'delete@example.com',
'password_hash' => password_hash('password123', PASSWORD_DEFAULT),
'name' => 'Delete User',
];
$id = $userModel->insert($data);
$userModel->delete($id);
$found = $userModel->find($id);
$this->assertNull($found);
}
/**
* Test: Alle Benutzer können abgerufen werden
*/
public function testAllUsersCanBeRetrieved(): void
{
$userModel = new UserModel();
// Insert mehrere Benutzer
for ($i = 1; $i <= 3; $i++) {
$userModel->insert([
'email' => "user{$i}@example.com",
'password_hash' => password_hash('password', PASSWORD_DEFAULT),
'name' => "User {$i}",
]);
}
$users = $userModel->findAll();
$this->assertCount(3, $users);
}
/**
* Test: Passwort Hash ist gültig
*/
public function testPasswordHashIsValid(): void
{
$userModel = new UserModel();
$password = 'mysecurepassword123';
$data = [
'email' => 'hash@example.com',
'password_hash' => password_hash($password, PASSWORD_DEFAULT),
'name' => 'Hash Test',
];
$userModel->insert($data);
$user = $userModel->where('email', 'hash@example.com')->first();
$this->assertTrue(password_verify($password, $user['password_hash']));
$this->assertFalse(password_verify('wrongpassword', $user['password_hash']));
}
}