user = $this->createUser(); Storage::fake('local'); $this->logPath = storage_path('app/mail.log'); Config::set('anonaddy.postfix_log_path', $this->logPath); Config::set('anonaddy.all_domains', ['anonaddy.me']); } protected function tearDown(): void { if (file_exists($this->logPath)) { unlink($this->logPath); } parent::tearDown(); } public function test_it_parses_rejection_lines_and_creates_failed_delivery_for_users() { $alias = Alias::factory()->create(['user_id' => $this->user->id, 'email' => 'test@anonaddy.me']); $logContent = "Mar 17 10:30:00 server postfix/smtpd[12345]: NOQUEUE: reject: RCPT from unknown[1.2.3.4]: 450 4.7.1 Client host rejected: cannot find your hostname; from= to= proto=ESMTP helo=<1.2.3.4>\n"; file_put_contents($this->logPath, $logContent); $this->artisan('anonaddy:parse-postfix-mail-log')->assertExitCode(0); $this->assertDatabaseHas('failed_deliveries', [ 'user_id' => $this->user->id, 'alias_id' => $alias->id, 'email_type' => 'IR', 'code' => '450 4.7.1 Client host rejected: cannot find your hostname', 'status' => '450', 'remote_mta' => 'unknown[1.2.3.4]', ]); $failedDelivery = FailedDelivery::first(); $this->assertEquals('s@x.com', $failedDelivery->sender); $this->assertEquals('test@anonaddy.me', $failedDelivery->destination); } public function test_it_skips_missing_alias() { $logContent = "Mar 17 10:30:00 server postfix/smtpd[12345]: NOQUEUE: reject: RCPT from unknown[1.2.3.4]: 450 4.7.1 Client host rejected; from= to= proto=ESMTP helo=<1.2.3.4>\n"; file_put_contents($this->logPath, $logContent); $this->artisan('anonaddy:parse-postfix-mail-log')->assertExitCode(0); $this->assertDatabaseCount('failed_deliveries', 0); } public function test_it_handles_log_rotation_and_maintains_position() { $alias = Alias::factory()->create(['user_id' => $this->user->id, 'email' => 'test@anonaddy.me']); $logContent1 = "Mar 17 10:30:00 server postfix/smtpd[12345]: NOQUEUE: reject: RCPT from unknown[1.2.3.4]: 450 4.7.1 Client host rejected; from= to=\n"; file_put_contents($this->logPath, $logContent1); $this->artisan('anonaddy:parse-postfix-mail-log')->assertExitCode(0); $this->assertDatabaseCount('failed_deliveries', 1); // Add a second line to same file $logContent2 = "Mar 17 10:31:00 server postfix/smtpd[12345]: NOQUEUE: reject: RCPT from unknown[1.2.3.4]: 450 4.7.1 Client host rejected; from= to=\n"; file_put_contents($this->logPath, $logContent1.$logContent2); $this->artisan('anonaddy:parse-postfix-mail-log')->assertExitCode(0); $this->assertDatabaseCount('failed_deliveries', 2); // Simulate log rotation (file smaller) $logContent3 = "Mar 17 10:32:00 server postfix/smtpd[12345]: NOQUEUE: reject: RCPT from unknown[1.2.3.4]: 450 4.7.1 Client host rejected; from= to=\n"; file_put_contents($this->logPath, $logContent3); $this->artisan('anonaddy:parse-postfix-mail-log')->assertExitCode(0); $this->assertDatabaseCount('failed_deliveries', 3); } public function test_it_prevents_duplicate_rejections() { $alias = Alias::factory()->create(['user_id' => $this->user->id, 'email' => 'test@anonaddy.me']); $logContent = "Mar 17 10:30:00 server postfix/smtpd[12345]: NOQUEUE: reject: RCPT from unknown[1.2.3.4]: 450 4.7.1 Client host rejected; from= to=\n"; file_put_contents($this->logPath, $logContent); $this->artisan('anonaddy:parse-postfix-mail-log')->assertExitCode(0); $this->assertDatabaseCount('failed_deliveries', 1); // Reset position to force re-reading the same line Storage::disk('local')->put('postfix_log_position.txt', '0'); $this->artisan('anonaddy:parse-postfix-mail-log')->assertExitCode(0); $this->assertDatabaseCount('failed_deliveries', 1); // Should not duplicate } public function test_it_parses_milter_reject_lines() { $alias = Alias::factory()->create(['user_id' => $this->user->id, 'email' => 'test@anonaddy.com']); $logContent = "Mar 18 12:53:05 mail2 postfix/cleanup[1661539]: 7EB9BFF16A: milter-reject: END-OF-MESSAGE from mx.abc.eu[86.106.123.126]: 5.7.1 Spam message rejected; from= to= proto=ESMTP helo=\n"; file_put_contents($this->logPath, $logContent); $this->artisan('anonaddy:parse-postfix-mail-log')->assertExitCode(0); $this->assertDatabaseHas('failed_deliveries', [ 'user_id' => $this->user->id, 'alias_id' => $alias->id, 'email_type' => 'IR', 'code' => '5.7.1 Spam message rejected', 'status' => '5.7.1', 'remote_mta' => 'mx.abc.eu[86.106.123.126]', 'bounce_type' => 'spam', ]); $failedDelivery = FailedDelivery::first(); $this->assertEquals('noreply@hi.market', $failedDelivery->sender); $this->assertEquals('test@anonaddy.com', $failedDelivery->destination); } public function test_it_parses_discard_lines() { $alias = Alias::factory()->create(['user_id' => $this->user->id, 'email' => 'caloric.test@anonaddy.com']); $logContent = "Mar 18 06:55:15 mail2 postfix/smtpd[1491842]: NOQUEUE: discard: RCPT from a.b.com[52.48.1.81]: : Recipient address is inactive alias; from= to= proto=SMTP helo=\n"; file_put_contents($this->logPath, $logContent); $this->artisan('anonaddy:parse-postfix-mail-log')->assertExitCode(0); $this->assertDatabaseHas('failed_deliveries', [ 'user_id' => $this->user->id, 'alias_id' => $alias->id, 'email_type' => 'IR', 'code' => 'Recipient address is inactive alias', 'status' => '', 'remote_mta' => 'a.b.com[52.48.1.81]', 'bounce_type' => 'hard', ]); $failedDelivery = FailedDelivery::first(); $this->assertEquals('takedown@b.com', $failedDelivery->sender); $this->assertEquals('caloric.test@anonaddy.com', $failedDelivery->destination); } }