Deploy HickLab3D Dashboard / Deploy to production (push) Successful in 28s
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
154 lines
6.6 KiB
PHP
154 lines
6.6 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature;
|
|
|
|
use App\Models\EtsyConnection;
|
|
use App\Models\User;
|
|
use App\Services\AlertScanner;
|
|
use Illuminate\Foundation\Testing\LazilyRefreshDatabase;
|
|
use Illuminate\Http\Client\Request;
|
|
use Illuminate\Support\Facades\Cache;
|
|
use Illuminate\Support\Facades\Http;
|
|
use Tests\TestCase;
|
|
|
|
class NotificationScannerTest extends TestCase
|
|
{
|
|
use LazilyRefreshDatabase;
|
|
|
|
/** @var array<int, array<string, mixed>> */
|
|
private array $receipts = [];
|
|
|
|
/** @var array<string, mixed> */
|
|
private array $live = [];
|
|
|
|
/** @var array<int, array<string, mixed>> */
|
|
private array $spools = [];
|
|
|
|
private User $admin;
|
|
|
|
private User $member;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
$this->admin = User::factory()->create(['role' => 'admin']);
|
|
$this->member = User::factory()->create(['role' => 'user']);
|
|
config()->set('services.etsy', ['keystring' => 'key', 'shared_secret' => 'secret']);
|
|
config()->set('services.bambuddy', ['url' => 'https://3d.example.test', 'api_key' => 'key']);
|
|
EtsyConnection::create(['shop_id' => 42, 'shop_name' => 'HickLab3D', 'access_token' => 'token', 'refresh_token' => 'refresh', 'expires_at' => now()->addHour()]);
|
|
Http::preventStrayRequests();
|
|
Http::fake(fn (Request $request) => match (true) {
|
|
str_contains($request->url(), '/receipts') => Http::response(['results' => $this->receipts]),
|
|
str_ends_with($request->url(), '/api/v1/printers/') => Http::response([['id' => 1, 'name' => 'Printy', 'model' => 'X1C']]),
|
|
str_ends_with($request->url(), '/printers/1/status') => Http::response($this->live),
|
|
str_contains($request->url(), '/inventory/spools') => Http::response($this->spools),
|
|
default => Http::response([], 404),
|
|
});
|
|
}
|
|
|
|
private function scan(): int
|
|
{
|
|
Cache::flush();
|
|
|
|
return app(AlertScanner::class)->scan();
|
|
}
|
|
|
|
/** @return array<string, mixed> */
|
|
private function receipt(int $id, ?int $shipBy = null): array
|
|
{
|
|
return [
|
|
'receipt_id' => $id, 'name' => 'Buyer '.$id, 'grandtotal' => ['amount' => 1250, 'divisor' => 100, 'currency_code' => 'GBP'],
|
|
'transactions' => $shipBy ? [['expected_ship_date' => $shipBy]] : [],
|
|
];
|
|
}
|
|
|
|
public function test_new_orders_notify_admins_only_and_the_first_scan_is_silent(): void
|
|
{
|
|
$this->receipts = [$this->receipt(1)];
|
|
$this->assertSame(0, $this->scan());
|
|
|
|
$this->receipts = [$this->receipt(2), $this->receipt(1)];
|
|
$this->assertSame(1, $this->scan());
|
|
$this->assertSame(0, $this->scan());
|
|
|
|
$notification = $this->admin->notifications()->first();
|
|
$this->assertSame('New Etsy order #2', $notification->data['title']);
|
|
$this->assertSame('Buyer 2 · GBP 12.50', $notification->data['body']);
|
|
$this->assertCount(0, $this->member->notifications);
|
|
}
|
|
|
|
public function test_orders_due_within_a_day_and_overdue_each_notify_once(): void
|
|
{
|
|
$this->receipts = [$this->receipt(7, time() + 3600 * 12)];
|
|
$this->assertSame(1, $this->scan());
|
|
$this->assertSame(0, $this->scan());
|
|
$this->assertSame('Order #7 is due to ship within 24 hours', $this->admin->notifications()->first()->data['title']);
|
|
|
|
$this->receipts = [$this->receipt(7, time() - 60)];
|
|
$this->assertSame(1, $this->scan());
|
|
$this->assertSame(0, $this->scan());
|
|
$this->assertContains('Order #7 is overdue to ship', $this->admin->notifications->pluck('data.title')->all());
|
|
|
|
$this->receipts = [$this->receipt(8, time() + 3600 * 72)];
|
|
$this->scan();
|
|
$this->assertSame(3, $this->admin->notifications()->count());
|
|
$this->assertContains('New Etsy order #8', $this->admin->fresh()->notifications->pluck('data.title')->all());
|
|
}
|
|
|
|
public function test_print_milestones_completion_and_errors_notify_everyone_once(): void
|
|
{
|
|
$this->live = ['connected' => true, 'state' => 'RUNNING', 'subtask_name' => 'Vase', 'progress' => 30.0, 'remaining_time' => 90, 'hms_errors' => []];
|
|
$this->assertSame(0, $this->scan());
|
|
|
|
$this->live['progress'] = 55.0;
|
|
$this->assertSame(1, $this->scan());
|
|
$this->assertSame(0, $this->scan());
|
|
$this->assertSame('Printy · Vase is 50% complete', $this->member->notifications()->first()->data['title']);
|
|
|
|
$this->live['progress'] = 80.0;
|
|
$this->assertSame(1, $this->scan());
|
|
|
|
$this->live['hms_errors'] = [['description' => 'Filament tangled']];
|
|
$this->assertSame(1, $this->scan());
|
|
$this->assertSame(0, $this->scan());
|
|
|
|
$this->live = ['connected' => true, 'state' => 'FINISH', 'subtask_name' => 'Vase', 'progress' => 100.0, 'hms_errors' => []];
|
|
$this->assertSame(1, $this->scan());
|
|
$this->assertSame(0, $this->scan());
|
|
$this->assertContains('Printy · Vase is 100% complete', $this->admin->notifications->pluck('data.title')->all());
|
|
}
|
|
|
|
public function test_failed_prints_notify_as_errors(): void
|
|
{
|
|
$this->live = ['connected' => true, 'state' => 'RUNNING', 'subtask_name' => 'Vase', 'progress' => 10.0, 'hms_errors' => []];
|
|
$this->scan();
|
|
$this->live['state'] = 'FAILED';
|
|
|
|
$this->assertSame(1, $this->scan());
|
|
$this->assertSame(0, $this->scan());
|
|
$notification = $this->admin->notifications()->first();
|
|
$this->assertSame('danger', $notification->data['level']);
|
|
$this->assertSame('Printy · Vase failed', $notification->data['title']);
|
|
}
|
|
|
|
public function test_low_filament_notifies_once_until_the_spool_is_refilled(): void
|
|
{
|
|
$this->spools = [
|
|
['id' => 1, 'color_name' => 'Azure', 'material' => 'ABS', 'brand' => 'Bambu Lab', 'label_weight' => 1000, 'weight_used' => 850, 'archived_at' => null],
|
|
['id' => 2, 'color_name' => 'Black', 'material' => 'PLA', 'label_weight' => 1000, 'weight_used' => 100, 'archived_at' => null],
|
|
['id' => 3, 'color_name' => 'Old', 'material' => 'PLA', 'label_weight' => 1000, 'weight_used' => 990, 'archived_at' => '2026-01-01T00:00:00'],
|
|
];
|
|
|
|
$this->assertSame(1, $this->scan());
|
|
$this->assertSame(0, $this->scan());
|
|
$notification = $this->admin->notifications()->first();
|
|
$this->assertSame('Low filament: Azure ABS', $notification->data['title']);
|
|
$this->assertSame('Bambu Lab · 15% (about 150 g) left', $notification->data['body']);
|
|
|
|
$this->spools[0]['weight_used'] = 100;
|
|
$this->scan();
|
|
$this->spools[0]['weight_used'] = 900;
|
|
$this->assertSame(1, $this->scan());
|
|
}
|
|
}
|