Migrate HickLab3D dashboard and add production auto deployment
Deploy HickLab3D Dashboard / Deploy to production (push) Failing after 19s
Deploy HickLab3D Dashboard / Deploy to production (push) Failing after 19s
This commit is contained in:
@@ -0,0 +1,209 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Models\EtsyConnection;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\LazilyRefreshDatabase;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Tests\TestCase;
|
||||
|
||||
class IntegrationsTest extends TestCase
|
||||
{
|
||||
use LazilyRefreshDatabase;
|
||||
|
||||
public function test_regular_user_cannot_begin_etsy_oauth(): void
|
||||
{
|
||||
$this->actingAs(User::factory()->create())->post(route('etsy.connect'))->assertForbidden();
|
||||
}
|
||||
|
||||
public function test_etsy_callback_rejects_mismatched_state_without_exchanging_token(): void
|
||||
{
|
||||
Http::preventStrayRequests();
|
||||
$admin = User::factory()->create(['role' => 'admin']);
|
||||
|
||||
$this->actingAs($admin)->withSession(['etsy_oauth' => ['state' => 'expected', 'verifier' => 'verifier', 'started' => time()]])
|
||||
->get(route('etsy.callback', ['state' => 'wrong', 'code' => 'code']))
|
||||
->assertRedirect(route('etsy.index'))->assertSessionHasErrors('etsy');
|
||||
Http::assertNothingSent();
|
||||
}
|
||||
|
||||
public function test_etsy_callback_saves_encrypted_tokens_and_shows_orders(): void
|
||||
{
|
||||
Http::preventStrayRequests();
|
||||
Http::fake([
|
||||
'https://api.etsy.com/v3/public/oauth/token' => Http::response(['access_token' => '123.token', 'refresh_token' => '123.refresh', 'expires_in' => 3600]),
|
||||
'https://api.etsy.com/v3/application/users/123/shops' => Http::response(['shop_id' => 42, 'shop_name' => 'HickLab3D']),
|
||||
'https://api.etsy.com/v3/application/shops/42/receipts*' => Http::response(['results' => [['receipt_id' => 77, 'name' => 'Buyer']]]),
|
||||
'https://api.etsy.com/v3/application/shops/42/listings*' => Http::response(['results' => [['title' => '3D print', 'quantity' => 5]]]),
|
||||
]);
|
||||
config()->set('services.etsy', ['keystring' => 'key', 'shared_secret' => 'secret', 'redirect_uri' => 'https://example.test/etsy/callback']);
|
||||
$admin = User::factory()->create(['role' => 'admin']);
|
||||
|
||||
$this->actingAs($admin)->withSession(['etsy_oauth' => ['state' => 'expected', 'verifier' => 'verifier', 'started' => time()]])
|
||||
->get(route('etsy.callback', ['state' => 'expected', 'code' => 'code']))
|
||||
->assertRedirect(route('etsy.index'));
|
||||
$connection = EtsyConnection::firstOrFail();
|
||||
$this->assertSame('123.token', $connection->access_token);
|
||||
$this->assertNotSame('123.token', $connection->getRawOriginal('access_token'));
|
||||
$this->actingAs($admin)->get(route('etsy.index'))->assertOk()->assertSee('3D print')->assertSee('#77');
|
||||
Http::assertSentCount(4);
|
||||
}
|
||||
|
||||
public function test_bambuddy_replaces_local_printer_status(): void
|
||||
{
|
||||
Http::preventStrayRequests();
|
||||
Http::fake([
|
||||
'https://3d.example.test/api/v1/printers' => Http::response([['id' => 1, 'name' => 'X1 Carbon', 'status' => 'printing']]),
|
||||
'https://3d.example.test/api/v1/printers/1/status' => Http::response(['connected' => true, 'state' => 'RUNNING', 'progress' => 45, 'temperatures' => ['nozzle' => 220, 'bed' => 60]]),
|
||||
]);
|
||||
config()->set('services.bambuddy', ['url' => 'https://3d.example.test', 'api_key' => 'read-key']);
|
||||
|
||||
$this->actingAs(User::factory()->create())->get(route('printers.index'))->assertOk()->assertSee('X1 Carbon')->assertSee('45%');
|
||||
Http::assertSent(fn ($request) => $request->hasHeader('X-API-Key', 'read-key'));
|
||||
}
|
||||
|
||||
public function test_shipping_quote_sends_parcel_to_parcel2go(): void
|
||||
{
|
||||
Http::preventStrayRequests();
|
||||
Http::fake([
|
||||
'https://auth.example.test/token' => Http::response(['access_token' => 'test-token']),
|
||||
'https://shipping.example.test/api/quotes' => Http::response(['Quotes' => [['Service' => ['Name' => 'Evri', 'Slug' => 'evri-collection', 'CollectionType' => 'Collection'], 'TotalPrice' => 5.5]]]),
|
||||
'https://shipping.example.test/api/me/detail' => Http::response([]),
|
||||
'https://shipping.example.test/api/me/prepay/balance' => Http::response([]),
|
||||
'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']);
|
||||
$admin = User::factory()->create(['role' => 'admin']);
|
||||
$details = ['collection_postcode' => 'SW1A 1AA', 'delivery_postcode' => 'M1 1AE', 'weight' => 2, 'length' => 10, 'width' => 10, 'height' => 10, 'value' => 30];
|
||||
|
||||
$this->actingAs($admin)->post(route('shipping.quote'), $details)->assertRedirect(route('shipping'));
|
||||
$this->actingAs($admin)->get(route('shipping'))->assertOk()->assertSee('Evri')->assertSee('£5.50');
|
||||
Http::assertSent(fn ($request) => $request->url() === 'https://shipping.example.test/api/quotes' && $request['Parcels'][0]['Weight'] === 2);
|
||||
}
|
||||
|
||||
public function test_shipping_label_is_downloaded_for_an_order_in_account_history(): void
|
||||
{
|
||||
Http::preventStrayRequests();
|
||||
Http::fake([
|
||||
'https://auth.example.test/token' => Http::response(['access_token' => 'test-token']),
|
||||
'https://shipping.example.test/api/me/orders/detail' => Http::response(['Orders' => [['OrderLineId' => 123, 'Hash' => 'order-hash']]]),
|
||||
'https://shipping.example.test/api/labels/123*' => Http::response(['Base64EncodedLabels' => [base64_encode('%PDF-1.4 test label')]]),
|
||||
]);
|
||||
config()->set('services.parcel2go', ['client_id' => 'id', 'client_secret' => 'secret', 'auth_url' => 'https://auth.example.test/token', 'base_url' => 'https://shipping.example.test/api']);
|
||||
|
||||
$this->actingAs(User::factory()->create(['role' => 'admin']))->get(route('shipping.label', 123))
|
||||
->assertOk()->assertHeader('content-type', 'application/pdf')->assertSee('%PDF-1.4 test label');
|
||||
Http::assertSent(fn ($request) => str_contains($request->url(), '/labels/123') && str_contains($request->url(), 'hash=order-hash'));
|
||||
}
|
||||
|
||||
public function test_collection_booking_uses_quoted_service_and_available_date(): void
|
||||
{
|
||||
Http::preventStrayRequests();
|
||||
Http::fake([
|
||||
'https://auth.example.test/token' => Http::response(['access_token' => 'test-token']),
|
||||
'https://shipping.example.test/api/orders' => Http::response(['OrderId' => '789', 'TotalPrice' => 8.5, 'Links' => ['payment' => 'https://www.parcel2go.com/order/payment?id=789']]),
|
||||
'https://shipping.example.test/api/me/detail' => Http::response([]),
|
||||
'https://shipping.example.test/api/me/prepay/balance' => Http::response([]),
|
||||
'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']);
|
||||
$session = [
|
||||
'shipping_quote' => ['collection_postcode' => 'SW1A 1AA', 'delivery_postcode' => 'M1 1AE', 'weight' => 2, 'length' => 10, 'width' => 10, 'height' => 10, 'value' => 30],
|
||||
'shipping_selected_service' => 'evri-collection',
|
||||
'shipping_quotes' => [['Service' => ['Slug' => 'evri-collection', 'CollectionType' => 'Collection']]],
|
||||
'shipping_collection_dates' => [['CollectionDate' => '2026-10-15T10:00:00Z']],
|
||||
'shipping_sender_location' => ['sender_street' => 'Main Street', 'sender_town' => 'London'],
|
||||
];
|
||||
$booking = [
|
||||
'collection_date' => '2026-10-15T10:00:00Z',
|
||||
'sender_name' => 'Lee Hickman', 'sender_email' => 'lee@example.test', 'sender_phone' => '07123456789',
|
||||
'sender_property' => '1', 'sender_street' => 'Main Street', 'sender_town' => 'London',
|
||||
'recipient_name' => 'Buyer Name', 'recipient_email' => 'buyer@example.test', 'recipient_phone' => '07123456788',
|
||||
'recipient_property' => '2', 'recipient_street' => 'Market Street', 'recipient_town' => 'Manchester', 'contents' => '3D printed part',
|
||||
];
|
||||
|
||||
$this->actingAs(User::factory()->create(['role' => 'admin']))->withSession($session)
|
||||
->post(route('shipping.book-collection'), $booking)->assertRedirect(route('shipping'))->assertSessionHas('shipping_booking_result.OrderId', '789');
|
||||
$this->get(route('shipping'))->assertOk()->assertSee('Collection order #789')->assertSee('Complete payment');
|
||||
Http::assertSent(fn ($request) => $request->url() === 'https://shipping.example.test/api/orders'
|
||||
&& $request['Items'][0]['Service'] === 'evri-collection' && $request['Items'][0]['Parcels'][0]['ContentsSummary'] === '3D printed part');
|
||||
}
|
||||
|
||||
public function test_collection_dates_only_accept_a_quoted_collection_service(): void
|
||||
{
|
||||
Http::preventStrayRequests();
|
||||
$admin = User::factory()->create(['role' => 'admin']);
|
||||
$session = [
|
||||
'shipping_quote' => ['collection_postcode' => 'SW1A 1AA'],
|
||||
'shipping_quotes' => [['Service' => ['Slug' => 'evri-collection', 'CollectionType' => 'Collection']]],
|
||||
];
|
||||
|
||||
$this->actingAs($admin)->withSession($session)
|
||||
->post(route('shipping.collection-dates'), ['service' => 'forged-service', 'sender_street' => 'Main Street', 'sender_town' => 'London'])
|
||||
->assertSessionHasErrors('service');
|
||||
Http::assertNothingSent();
|
||||
}
|
||||
|
||||
public function test_collection_dates_are_loaded_for_a_quoted_service(): void
|
||||
{
|
||||
Http::preventStrayRequests();
|
||||
Http::fake([
|
||||
'https://auth.example.test/token' => Http::response(['access_token' => 'test-token']),
|
||||
'https://shipping.example.test/api/collectiondates' => Http::response(['CollectionDates' => [['CollectionDate' => '2026-10-15T10:00:00Z']]]),
|
||||
]);
|
||||
config()->set('services.parcel2go', ['client_id' => 'id', 'client_secret' => 'secret', 'auth_url' => 'https://auth.example.test/token', 'base_url' => 'https://shipping.example.test/api']);
|
||||
$session = [
|
||||
'shipping_quote' => ['collection_postcode' => 'SW1A 1AA'],
|
||||
'shipping_quotes' => [['Service' => ['Slug' => 'evri-collection', 'CollectionType' => 'Collection']]],
|
||||
];
|
||||
|
||||
$this->actingAs(User::factory()->create(['role' => 'admin']))->withSession($session)
|
||||
->post(route('shipping.collection-dates'), ['service' => 'evri-collection', 'sender_street' => 'Main Street', 'sender_town' => 'London'])
|
||||
->assertRedirect(route('shipping'))->assertSessionHas('shipping_selected_service', 'evri-collection');
|
||||
Http::assertSent(fn ($request) => $request->url() === 'https://shipping.example.test/api/collectiondates'
|
||||
&& $request['ServiceSlug'] === 'evri-collection' && $request['Postcode'] === 'SW1A 1AA');
|
||||
}
|
||||
|
||||
public function test_uncertain_booking_requires_account_check_before_a_new_quote(): void
|
||||
{
|
||||
Http::preventStrayRequests();
|
||||
$admin = User::factory()->create(['role' => 'admin']);
|
||||
$details = ['collection_postcode' => 'SW1A 1AA', 'delivery_postcode' => 'M1 1AE', 'weight' => 2, 'length' => 10, 'width' => 10, 'height' => 10, 'value' => 30];
|
||||
|
||||
$this->actingAs($admin)->withSession(['shipping_booking_pending' => true])
|
||||
->post(route('shipping.quote'), $details)->assertSessionHasErrors('collection');
|
||||
$this->actingAs($admin)->post(route('shipping.reset-booking'), [])->assertSessionHasErrors('checked_account');
|
||||
$this->actingAs($admin)->post(route('shipping.reset-booking'), ['checked_account' => 1])
|
||||
->assertRedirect(route('shipping'))->assertSessionMissing('shipping_booking_pending');
|
||||
Http::assertNothingSent();
|
||||
}
|
||||
|
||||
public function test_dryer_status_and_fault_acknowledgement_use_versioned_api(): void
|
||||
{
|
||||
Http::preventStrayRequests();
|
||||
Http::fake([
|
||||
'https://dryer.example.test/api/v1/status' => Http::response(['state' => 'idle', 'sample' => ['valid' => true, 'temperatureC' => 25]]),
|
||||
'https://dryer.example.test/api/v1/history*' => Http::response(['samples' => []]),
|
||||
'https://dryer.example.test/api/v1/faults/acknowledge' => 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)->get(route('dryer.status'))->assertOk()->assertJsonPath('status.state', 'idle');
|
||||
$this->actingAs($admin)->postJson(route('dryer.acknowledge'))->assertOk()->assertJsonPath('ok', true);
|
||||
Http::assertSent(fn ($request) => $request->url() === 'https://dryer.example.test/api/v1/faults/acknowledge'
|
||||
&& $request->hasHeader('X-API-Key', 'test-key') && str_starts_with($request['requestId'], 'dashboard-'));
|
||||
}
|
||||
|
||||
public function test_dryer_rejects_temperature_above_firmware_limit_without_contacting_device(): void
|
||||
{
|
||||
Http::preventStrayRequests();
|
||||
$admin = User::factory()->create(['role' => 'admin']);
|
||||
|
||||
$this->actingAs($admin)->postJson(route('dryer.control'), [
|
||||
'dryerOn' => true, 'profile' => 'PLA', 'targetTemperature' => 80, 'dryingTime' => 240,
|
||||
])->assertUnprocessable()->assertJsonValidationErrors('targetTemperature');
|
||||
Http::assertNothingSent();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user