205 lines
11 KiB
PHP
205 lines
11 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature;
|
|
|
|
use App\Models\EtsyConnection;
|
|
use App\Models\Printer;
|
|
use App\Models\User;
|
|
use Illuminate\Foundation\Testing\LazilyRefreshDatabase;
|
|
use Illuminate\Support\Facades\Hash;
|
|
use Illuminate\Support\Facades\Http;
|
|
use Tests\TestCase;
|
|
|
|
class DashboardAccessTest extends TestCase
|
|
{
|
|
use LazilyRefreshDatabase;
|
|
|
|
public function test_guest_is_sent_to_login(): void
|
|
{
|
|
$this->get(route('dashboard'))->assertRedirect(route('login'));
|
|
}
|
|
|
|
public function test_authenticated_user_sees_hicklab3d_dashboard(): void
|
|
{
|
|
$user = User::factory()->create(['username' => 'maker']);
|
|
|
|
$this->actingAs($user)->get(route('dashboard'))->assertOk()->assertSee('HickLab3D')->assertSee('maker');
|
|
}
|
|
|
|
public function test_administrator_sees_live_shop_and_workshop_overview(): void
|
|
{
|
|
Http::preventStrayRequests();
|
|
Http::fake([
|
|
'https://api.etsy.com/v3/application/shops/42/receipts*' => Http::response(['results' => [
|
|
['receipt_id' => 77, 'name' => 'Buyer One', 'create_timestamp' => now()->timestamp],
|
|
]]),
|
|
'https://api.etsy.com/v3/application/shops/42/listings*' => Http::response(['count' => 12, 'results' => []]),
|
|
'https://3d.example.test/api/v1/printers' => Http::response([['id' => 1, 'name' => 'X1 Carbon']]),
|
|
'https://3d.example.test/api/v1/printers/1/status' => Http::response(['connected' => true, 'state' => 'RUNNING', 'progress' => 45]),
|
|
'https://auth.example.test/token' => Http::response(['access_token' => 'shipping-token']),
|
|
'https://shipping.example.test/api/me/orders/detail' => Http::response(['Orders' => [
|
|
['OrderLineId' => 91, 'Courier' => 'Evri', 'Tracking' => ['Delivered' => false]],
|
|
]]),
|
|
'https://dryer.example.test/api/v1/status' => Http::response(['state' => 'idle']),
|
|
]);
|
|
config()->set('services.etsy', ['keystring' => 'key', 'shared_secret' => 'secret']);
|
|
config()->set('services.bambuddy', ['url' => 'https://3d.example.test', 'api_key' => 'key']);
|
|
config()->set('services.parcel2go', [
|
|
'client_id' => 'id', 'client_secret' => 'secret',
|
|
'auth_url' => 'https://auth.example.test/token', 'base_url' => 'https://shipping.example.test/api',
|
|
'scope' => 'public-api my-profile',
|
|
]);
|
|
config()->set('services.dryer', ['url' => 'https://dryer.example.test/api/v1', 'api_key' => 'key']);
|
|
EtsyConnection::create(['shop_id' => 42, 'shop_name' => 'HickLab3D', 'access_token' => '123.token', 'refresh_token' => '123.refresh', 'expires_at' => now()->addHour()]);
|
|
$admin = User::factory()->create(['role' => 'admin']);
|
|
|
|
$this->actingAs($admin)->get(route('dashboard'))->assertOk()
|
|
->assertSee('Awaiting dispatch')->assertSee('#77')->assertSee('12')
|
|
->assertSee('X1 Carbon')->assertSee('RUNNING')->assertSee('Print farm status')
|
|
->assertSee('#91')->assertSee('data-dashboard-chart="orders"', false)
|
|
->assertSee('data-dashboard-chart="printers"', false)
|
|
->assertViewHas('printerChart', [['label' => 'Printing', 'count' => 1]])
|
|
->assertViewHas('orderChart', fn (array $chart): bool => end($chart['series']) === 1);
|
|
}
|
|
|
|
public function test_regular_user_does_not_request_or_see_admin_shop_data(): void
|
|
{
|
|
Http::preventStrayRequests();
|
|
config()->set('services.bambuddy.api_key', null);
|
|
config()->set('services.dryer.api_key', null);
|
|
|
|
$this->actingAs(User::factory()->create())->get(route('dashboard'))->assertOk()
|
|
->assertDontSee('Orders to pack')->assertDontSee('Recent shipments')
|
|
->assertDontSee('Awaiting dispatch')->assertSee('No printer status to chart');
|
|
Http::assertNothingSent();
|
|
}
|
|
|
|
public function test_etsy_dispatch_stays_visible_when_listings_are_unavailable(): void
|
|
{
|
|
Http::preventStrayRequests();
|
|
Http::fake([
|
|
'https://api.etsy.com/v3/application/shops/42/receipts*' => Http::response(['results' => [
|
|
['receipt_id' => 77, 'name' => 'Buyer One', 'create_timestamp' => now()->timestamp],
|
|
]]),
|
|
'https://api.etsy.com/v3/application/shops/42/listings*' => Http::response([], 503),
|
|
]);
|
|
config()->set('services.etsy', ['keystring' => 'key', 'shared_secret' => 'secret']);
|
|
config()->set('services.bambuddy.api_key', null);
|
|
config()->set('services.parcel2go.client_id', null);
|
|
config()->set('services.dryer.api_key', null);
|
|
EtsyConnection::create(['shop_id' => 42, 'shop_name' => 'HickLab3D', 'access_token' => '123.token', 'refresh_token' => '123.refresh', 'expires_at' => now()->addHour()]);
|
|
|
|
$this->actingAs(User::factory()->create(['role' => 'admin']))->get(route('dashboard'))
|
|
->assertOk()->assertSee('#77')->assertSee('data-dashboard-chart="orders"', false)
|
|
->assertViewHas('listingCount', null);
|
|
}
|
|
|
|
public function test_unavailable_integrations_do_not_make_overview_fail(): void
|
|
{
|
|
Http::preventStrayRequests();
|
|
Http::fake([
|
|
'https://api.etsy.com/*' => Http::response([], 503),
|
|
'https://3d.example.test/*' => Http::response([], 503),
|
|
'https://auth.example.test/*' => Http::response([], 503),
|
|
'https://dryer.example.test/*' => Http::response([], 503),
|
|
]);
|
|
config()->set('services.etsy', ['keystring' => 'key', 'shared_secret' => 'secret']);
|
|
config()->set('services.bambuddy', ['url' => 'https://3d.example.test', 'api_key' => 'key']);
|
|
config()->set('services.parcel2go', [
|
|
'client_id' => 'id', 'client_secret' => 'secret',
|
|
'auth_url' => 'https://auth.example.test/token', 'base_url' => 'https://shipping.example.test/api',
|
|
]);
|
|
config()->set('services.dryer', ['url' => 'https://dryer.example.test/api/v1', 'api_key' => 'key']);
|
|
EtsyConnection::create(['shop_id' => 42, 'shop_name' => 'HickLab3D', 'access_token' => '123.token', 'refresh_token' => '123.refresh', 'expires_at' => now()->addHour()]);
|
|
|
|
$this->actingAs(User::factory()->create(['role' => 'admin']))->get(route('dashboard'))
|
|
->assertOk()->assertSee('Temporarily unavailable')->assertSee('No printer status to chart')
|
|
->assertDontSee('data-dashboard-chart="orders"', false);
|
|
}
|
|
|
|
public function test_disabled_account_cannot_sign_in(): void
|
|
{
|
|
User::factory()->create(['username' => 'disabled-user', 'disabled' => true, 'password' => 'correct-password']);
|
|
|
|
$this->post(route('login.store'), ['username' => 'disabled-user', 'password' => 'correct-password'])
|
|
->assertSessionHasErrors('username');
|
|
$this->assertGuest();
|
|
}
|
|
|
|
public function test_removed_filament_tracker_routes_are_unavailable(): void
|
|
{
|
|
$admin = User::factory()->create(['role' => 'admin']);
|
|
|
|
$this->actingAs($admin)->get('/filaments')->assertNotFound();
|
|
$this->actingAs($admin)->get('/filaments/create')->assertNotFound();
|
|
$this->actingAs($admin)->post('/filaments', ['filamentName' => 'PLA Black'])->assertNotFound();
|
|
}
|
|
|
|
public function test_administrator_can_manage_user_without_replacing_password(): void
|
|
{
|
|
$admin = User::factory()->create(['role' => 'admin']);
|
|
$member = User::factory()->create(['password' => 'original-password']);
|
|
|
|
$this->actingAs($admin)->patch(route('users.update', $member), [
|
|
'email' => 'new@example.test', 'role' => 'user', 'password' => '',
|
|
])->assertRedirect();
|
|
$this->assertDatabaseHas('users', ['id' => $member->id, 'email' => 'new@example.test']);
|
|
$this->assertTrue(Hash::check('original-password', $member->fresh()->password));
|
|
}
|
|
|
|
public function test_user_cannot_remove_another_users_printer(): void
|
|
{
|
|
$owner = User::factory()->create();
|
|
$other = User::factory()->create();
|
|
$printer = Printer::create(['userId' => $owner->id, 'printerName' => 'X1', 'printerIp' => '192.168.1.10', 'serialNumber' => 'X1TEST', 'mqttPassword' => 'secret']);
|
|
|
|
$this->actingAs($other)->delete(route('printers.destroy', $printer))->assertNotFound();
|
|
$this->assertModelExists($printer);
|
|
}
|
|
|
|
public function test_dryer_control_is_restricted_to_admins(): void
|
|
{
|
|
Http::preventStrayRequests();
|
|
$user = User::factory()->create();
|
|
|
|
$this->actingAs($user)->postJson(route('dryer.control'), ['dryerOn' => true, 'targetTemperature' => 45, 'dryingTime' => 240])->assertForbidden();
|
|
Http::assertNothingSent();
|
|
}
|
|
|
|
public function test_administrator_can_control_dryer_through_server_proxy(): void
|
|
{
|
|
Http::preventStrayRequests();
|
|
Http::fake(['https://dryer.example.test/api/v1/cycles/start' => Http::response(['ok' => true])]);
|
|
config()->set('services.dryer', ['url' => 'https://dryer.example.test/api/v1', 'api_key' => 'test-key']);
|
|
$admin = User::factory()->create(['role' => 'admin']);
|
|
|
|
$this->actingAs($admin)->postJson(route('dryer.control'), ['dryerOn' => true, 'profile' => 'PLA', 'targetTemperature' => 50, 'dryingTime' => 240])
|
|
->assertOk()->assertJsonPath('ok', true);
|
|
Http::assertSent(fn ($request) => $request->hasHeader('X-API-Key', 'test-key')
|
|
&& $request['profile'] === 'PLA' && $request['targetC'] === 50 && str_starts_with($request['requestId'], 'dashboard-'));
|
|
}
|
|
|
|
public function test_administrator_sees_shipping_data_from_parcel2go(): void
|
|
{
|
|
Http::preventStrayRequests();
|
|
Http::fake([
|
|
'https://auth.example.test/token' => Http::response(['access_token' => 'test-token']),
|
|
'https://shipping.example.test/api/me/detail' => Http::response(['Forename' => 'Lee', 'Surname' => 'Hickman']),
|
|
'https://shipping.example.test/api/me/prepay/balance' => Http::response(['Balance' => 12.5]),
|
|
'https://shipping.example.test/api/me/orders/detail' => Http::response(['Orders' => []]),
|
|
]);
|
|
config()->set('services.parcel2go', [
|
|
'client_id' => 'id', 'client_secret' => 'secret',
|
|
'auth_url' => 'https://auth.example.test/token',
|
|
'base_url' => 'https://shipping.example.test/api',
|
|
'scope' => 'public-api my-profile',
|
|
]);
|
|
$admin = User::factory()->create(['role' => 'admin']);
|
|
|
|
$this->actingAs($admin)->get(route('shipping'))->assertOk()->assertSee('Lee Hickman')->assertSee('12.50');
|
|
Http::assertSentCount(4);
|
|
Http::assertSent(fn ($request) => $request->url() === 'https://auth.example.test/token'
|
|
&& $request['scope'] === 'public-api my-profile');
|
|
}
|
|
}
|