Spaces:
Running
Running
| namespace Tests\Feature; | |
| use Illuminate\Support\Facades\Http; | |
| use Tests\TestCase; | |
| class MdSimulationE2ETest extends TestCase | |
| { | |
| private string $baseUrl; | |
| private string $email; | |
| private string $password; | |
| private string $name; | |
| protected function setUp(): void | |
| { | |
| parent::setUp(); | |
| $this->baseUrl = rtrim( | |
| (string) getenv('MD_E2E_BASE_URL') ?: 'http://localhost:7860/api', | |
| '/' | |
| ); | |
| $this->email = (string) getenv('MD_E2E_EMAIL') ?: 'test@example.com'; | |
| $this->password = (string) getenv('MD_E2E_PASSWORD') ?: 'password123'; | |
| $this->name = (string) getenv('MD_E2E_NAME') ?: 'Test User'; | |
| } | |
| // ββ Health check (no auth) ββββββββββββββββββββββββββββββββ | |
| public function test_health_check(): void | |
| { | |
| $response = Http::timeout(30)->get("{$this->baseUrl}/md-simulation/health"); | |
| $this->assertTrue( | |
| $response->successful(), | |
| 'Health check failed: ' . $response->body() | |
| ); | |
| $this->assertSame('ok', $response->json('status')); | |
| } | |
| // ββ Auth rejection (all protected endpoints) ββββββββββββββ | |
| public function test_unauthenticated_access_rejected(): void | |
| { | |
| $endpoints = [ | |
| ['GET', '/md-simulation/history'], | |
| ['POST', '/md-simulation/process'], | |
| ['GET', '/md-simulation/status/test-123'], | |
| ['POST', '/md-simulation/analyze/test-123'], | |
| ['GET', '/md-simulation/download/test-123'], | |
| ['GET', '/md-simulation/download-analysis/test-123'], | |
| ]; | |
| foreach ($endpoints as [$method, $uri]) { | |
| $response = match ($method) { | |
| 'GET' => Http::get("{$this->baseUrl}{$uri}"), | |
| 'POST' => Http::post("{$this->baseUrl}{$uri}", []), | |
| }; | |
| $this->assertEquals( | |
| 401, | |
| $response->status(), | |
| "Expected 401 for {$method} {$uri}, got {$response->status()}: {$response->body()}" | |
| ); | |
| } | |
| } | |
| // ββ Auth flow: login β token β authenticated request βββββ | |
| public function test_auth_flow_and_history(): void | |
| { | |
| $token = $this->authenticate(); | |
| $response = Http::withHeaders([ | |
| 'Authorization' => "Bearer {$token}", | |
| 'Accept' => 'application/json', | |
| ])->get("{$this->baseUrl}/md-simulation/history"); | |
| $this->assertTrue( | |
| $response->successful(), | |
| 'History request failed: ' . $response->body() | |
| ); | |
| $data = $response->json(); | |
| $this->assertTrue($data['success'] ?? false); | |
| $this->assertArrayHasKey('results', $data['data'] ?? []); | |
| $this->assertArrayHasKey('pagination', $data['data'] ?? []); | |
| } | |
| // ββ Validation: process without files βββββββββββββββββββββ | |
| public function test_process_validation_no_files(): void | |
| { | |
| $token = $this->authenticate(); | |
| $response = Http::withHeaders([ | |
| 'Authorization' => "Bearer {$token}", | |
| 'Accept' => 'application/json', | |
| ])->post("{$this->baseUrl}/md-simulation/process", []); | |
| $this->assertEquals( | |
| 422, | |
| $response->status(), | |
| 'Expected 422, got ' . $response->status() . ': ' . $response->body() | |
| ); | |
| } | |
| // ββ Validation: wrong file type ββββββββββββββββββββββββββ | |
| public function test_process_rejects_invalid_file_type(): void | |
| { | |
| $token = $this->authenticate(); | |
| $phpFile = base_path('tests/Feature/MdSimulationE2ETest.php'); | |
| $ligandPath = base_path('scripts/ligand.pdb'); | |
| $this->assertFileExists($phpFile); | |
| $this->assertFileExists($ligandPath); | |
| $response = Http::withHeaders([ | |
| 'Authorization' => "Bearer {$token}", | |
| ])->timeout(30)->attach( | |
| 'protein', fopen($phpFile, 'r'), 'not_a_pdb.txt' | |
| )->attach( | |
| 'ligand', fopen($ligandPath, 'r'), 'ligand.pdb' | |
| )->post("{$this->baseUrl}/md-simulation/process", []); | |
| $this->assertEquals( | |
| 422, | |
| $response->status(), | |
| 'Expected 422, got ' . $response->status() . ': ' . $response->body() | |
| ); | |
| } | |
| // ββ Full submit + status flow ββββββββββββββββββββββββββββ | |
| public function test_full_submit_and_status_flow(): void | |
| { | |
| $token = $this->authenticate(); | |
| $proteinPath = base_path('scripts/protein.pdb'); | |
| $ligandPath = base_path('scripts/ligand.pdb'); | |
| $this->assertFileExists($proteinPath); | |
| $this->assertFileExists($ligandPath); | |
| $submitResponse = Http::withHeaders([ | |
| 'Authorization' => "Bearer {$token}", | |
| ])->timeout(120)->attach( | |
| 'protein', fopen($proteinPath, 'r'), 'protein.pdb' | |
| )->attach( | |
| 'ligand', fopen($ligandPath, 'r'), 'ligand.pdb' | |
| )->post("{$this->baseUrl}/md-simulation/process", [ | |
| 'force_field' => 'ff19SB', | |
| 'sim_time_ns' => 0.01, | |
| 'equil_time_ns' => 0.1, | |
| 'temperature_k' => 300, | |
| ]); | |
| $this->assertEquals( | |
| 202, | |
| $submitResponse->status(), | |
| 'Submit failed: ' . $submitResponse->body() | |
| ); | |
| $submitData = $submitResponse->json(); | |
| $this->assertTrue($submitData['success'] ?? false); | |
| $this->assertArrayHasKey('remote_job_id', $submitData['data'] ?? []); | |
| $this->assertArrayHasKey('status', $submitData['data'] ?? []); | |
| $remoteJobId = $submitData['data']['remote_job_id']; | |
| $this->assertNotNull($remoteJobId); | |
| // Single status check β proves the endpoint works without waiting | |
| $statusResponse = Http::withHeaders([ | |
| 'Authorization' => "Bearer {$token}", | |
| ])->timeout(30)->get("{$this->baseUrl}/md-simulation/status/{$remoteJobId}"); | |
| $this->assertTrue( | |
| $statusResponse->successful(), | |
| 'Status check failed: ' . $statusResponse->body() | |
| ); | |
| $statusData = $statusResponse->json(); | |
| $this->assertTrue($statusData['success'] ?? false); | |
| $this->assertSame( | |
| $remoteJobId, | |
| $statusData['data']['remote_job_id'] ?? null | |
| ); | |
| } | |
| // ββ Helpers βββββββββββββββββββββββββββββββββββββββββββββββ | |
| private function authenticate(): string | |
| { | |
| $loginResponse = Http::timeout(30)->post("{$this->baseUrl}/user/login", [ | |
| 'email' => $this->email, | |
| 'password' => $this->password, | |
| ]); | |
| if ($loginResponse->successful()) { | |
| $token = $loginResponse->json('data.token'); | |
| $this->assertNotNull($token, 'Login succeeded but no token returned'); | |
| return $token; | |
| } | |
| // User may not exist yet β try registering | |
| $registerResponse = Http::timeout(30)->post( | |
| "{$this->baseUrl}/user/register", | |
| [ | |
| 'name' => $this->name, | |
| 'email' => $this->email, | |
| 'password' => $this->password, | |
| 'password_confirmation' => $this->password, | |
| ] | |
| ); | |
| $this->assertTrue( | |
| $registerResponse->successful(), | |
| 'Cannot authenticate: login returned ' . $loginResponse->status() | |
| . ', register returned ' . $registerResponse->status() | |
| . ': ' . $registerResponse->body() | |
| ); | |
| // Login after successful registration | |
| $loginResponse = Http::timeout(30)->post("{$this->baseUrl}/user/login", [ | |
| 'email' => $this->email, | |
| 'password' => $this->password, | |
| ]); | |
| $this->assertTrue( | |
| $loginResponse->successful(), | |
| 'Login failed after registration: ' . $loginResponse->body() | |
| ); | |
| $token = $loginResponse->json('data.token'); | |
| $this->assertNotNull($token); | |
| return $token; | |
| } | |
| } | |