agents = new AgentRepository($db); $this->storageDir = sys_get_temp_dir() . '/monitor-recordings-controller-' . uniqid(); $this->recordings = new VoiceRecordingRepository($db, $this->storageDir); $this->controller = new VoiceRecordingController($this->recordings); $this->tmpUploadFile = sys_get_temp_dir() . '/monitor-upload-' . uniqid(); file_put_contents($this->tmpUploadFile, 'echte audiodaten'); } protected function tearDown(): void { if (file_exists($this->tmpUploadFile)) { unlink($this->tmpUploadFile); } if (is_dir($this->storageDir)) { foreach (glob($this->storageDir . '/*') ?: [] as $file) { unlink($file); } rmdir($this->storageDir); } } /** @return array ein $_FILES-artiges Feld fuer Request::create(). */ private function uploadedAudioField(int $error = UPLOAD_ERR_OK): array { return [ 'name' => 'aufnahme.webm', 'type' => 'audio/webm', 'tmp_name' => $this->tmpUploadFile, 'error' => $error, 'size' => filesize($this->tmpUploadFile), ]; } /** Der Voice-Bot: die Rolle, die die Warteschlange tatsaechlich abholt. */ private function voiceBot(): array { return $this->agents->register('Vox', 'voice')['agent']; } public function testUploadRejectsMissingFile(): void { $response = $this->controller->upload(Request::create('POST', '/x')); self::assertSame(422, $response->status); } public function testUploadRejectsTooLargeUploadError(): void { $response = $this->controller->upload( Request::create('POST', '/x', files: ['audio' => $this->uploadedAudioField(UPLOAD_ERR_INI_SIZE)]) ); self::assertSame(413, $response->status); } /** * Der Upload kennt keinen Empfaenger mehr: kein Agent im Pfad, keine agent_id in der * Antwort - die Aufnahme liegt danach in der Warteschlange fuer alle. */ public function testUploadStoresRecordingWithoutAgent(): void { $response = $this->controller->upload( Request::create('POST', '/x', files: ['audio' => $this->uploadedAudioField()]) ); self::assertSame(201, $response->status); self::assertArrayNotHasKey('agent_id', $response->body()['recording']); self::assertSame('new', $response->body()['recording']['status']); } public function testIndexListsTheSharedQueue(): void { $this->recordings->store('a', 'audio/webm'); $response = $this->controller->index(Request::create('GET', '/x'), $this->voiceBot()); self::assertSame(200, $response->status); self::assertCount(1, $response->body()['recordings']); } public function testIndexFiltersByStatus(): void { $done = $this->recordings->store('a', 'audio/webm'); $this->recordings->store('b', 'audio/webm'); $this->recordings->markDone((int) $done['id']); $response = $this->controller->index( Request::create('GET', '/x', query: ['status' => 'new']), $this->voiceBot() ); self::assertSame(200, $response->status); self::assertCount(1, $response->body()['recordings']); } public function testIndexRejectsUnknownStatus(): void { $response = $this->controller->index( Request::create('GET', '/x', query: ['status' => 'archived']), $this->voiceBot() ); self::assertSame(400, $response->status); } /** * Jeder authentifizierte Agent kommt an jede Aufnahme - frueher hing der Zugriff an der * agent_id der Aufnahme, und genau daran blieb in Produktion eine Aufnahme haengen, die * an einen Agenten ohne Whisper adressiert war. */ public function testDownloadReturnsAudioBytesForAnyAgent(): void { $recording = $this->recordings->store('echte audiodaten', 'audio/webm'); $otherAgent = $this->agents->register('OPs', 'ops')['agent']; $response = $this->controller->download( Request::create('GET', '/x'), ['id' => (string) $recording['id']], $otherAgent ); self::assertSame(200, $response->status); self::assertSame('echte audiodaten', $response->body()); } public function testDownloadReturns404ForUnknownRecording(): void { $response = $this->controller->download( Request::create('GET', '/x'), ['id' => '999'], $this->voiceBot() ); self::assertSame(404, $response->status); } public function testMarkProcessedRejectsUnsupportedStatus(): void { $recording = $this->recordings->store('a', 'audio/webm'); $response = $this->controller->markProcessed( Request::create('PATCH', '/x', body: ['status' => 'archived']), ['id' => (string) $recording['id']], $this->voiceBot() ); self::assertSame(422, $response->status); } public function testMarkProcessedReturns404ForUnknownRecording(): void { $response = $this->controller->markProcessed( Request::create('PATCH', '/x', body: ['status' => 'done']), ['id' => '999'], $this->voiceBot() ); self::assertSame(404, $response->status); } public function testMarkProcessedMarksRecordingDone(): void { $recording = $this->recordings->store('a', 'audio/webm'); $response = $this->controller->markProcessed( Request::create('PATCH', '/x', body: ['status' => 'done']), ['id' => (string) $recording['id']], $this->voiceBot() ); self::assertSame(200, $response->status); self::assertSame('done', $response->body()['recording']['status']); } }