shouldReceive('getAvailableImages')->andReturn([ [ 'id' => 'ubuntu-24-image', 'name' => 'Ubuntu 24.04', 'description' => 'Ubuntu', 'os_type' => 'linux', 'standard_image' => true, ], [ 'id' => 'ubuntu-image', 'name' => 'Ubuntu 22.04', 'description' => 'Ubuntu', 'os_type' => 'linux', 'standard_image' => true, ], [ 'id' => 'win-image', 'name' => 'Windows Server 2022', 'description' => 'Windows', 'os_type' => 'windows', 'standard_image' => true, ], ]); $provider->shouldReceive('getAvailableProducts')->andReturn([ ['id' => 'V91', 'name' => 'Cloud VPS 10 NVMe', 'cpu' => 3, 'ram_gb' => 8, 'disk_gb' => 75, 'price_monthly' => 4.99], ['id' => 'V45', 'name' => 'VPS S SSD', 'cpu' => 4, 'ram_gb' => 8, 'disk_gb' => 200, 'price_monthly' => 6.99], ]); $this->app->instance(ContaboProvider::class, $provider); } public function test_vps_page_renders_dynamic_ghs_price_instead_of_stored_zero_value(): void { $user = User::factory()->create(); $this->mockContaboImages(); $this->mock(ExchangeRateService::class, function ($mock): void { $mock->shouldReceive('getUsdToGhs')->andReturn(15.50); }); HostingProduct::create([ 'name' => 'VPS S', 'slug' => 'vps-s-test', 'description' => 'Dynamic VPS pricing test', 'category' => 'vps', 'type' => 'vps', 'price_monthly' => 0, 'price_quarterly' => 0, 'price_yearly' => 0, 'currency' => 'USD', 'cpu_cores' => 4, 'ram_mb' => 8192, 'disk_gb' => 200, 'contabo_product_id' => 'V45', 'is_active' => true, 'is_visible' => true, ]); $request = Request::create('http://localhost/hosting/vps', 'GET'); $request->setUserResolver(fn (): User => $user); $view = $this->app->make(HostingProductController::class)->vps($request); $html = $view->render(); $this->assertStringContainsString('GHS 140.00', $html); $this->assertStringNotContainsString('$0.00', $html); } public function test_vps_page_lists_install_later_and_multiple_live_images(): void { $user = User::factory()->create(); $this->mockContaboImages(); $this->mock(ExchangeRateService::class, function ($mock): void { $mock->shouldReceive('getUsdToGhs')->andReturn(15.50); }); HostingProduct::create([ 'name' => 'VPS S', 'slug' => 'vps-s-install-later-test', 'description' => 'Dynamic VPS pricing test', 'category' => 'vps', 'type' => 'vps', 'price_monthly' => 0, 'price_quarterly' => 0, 'price_yearly' => 0, 'currency' => 'USD', 'cpu_cores' => 4, 'ram_mb' => 8192, 'disk_gb' => 200, 'contabo_product_id' => 'V45', 'is_active' => true, 'is_visible' => true, ]); $request = Request::create('http://localhost/hosting/vps', 'GET'); $request->setUserResolver(fn (): User => $user); $view = $this->app->make(HostingProductController::class)->vps($request); $html = $view->render(); $this->assertStringContainsString('Install Later', $html); $this->assertStringContainsString('Ubuntu 24.04', $html); $this->assertStringContainsString('Ubuntu 22.04', $html); } public function test_server_orders_use_display_currency_for_dynamic_products(): void { $user = User::factory()->create(); $this->mockContaboImages(); $this->mock(ExchangeRateService::class, function ($mock): void { $mock->shouldReceive('getUsdToGhs')->andReturn(15.50); }); $product = HostingProduct::create([ 'name' => 'VPS S', 'slug' => 'vps-s-order-test', 'description' => 'Dynamic VPS order test', 'category' => 'vps', 'type' => 'vps', 'price_monthly' => 0, 'price_quarterly' => 0, 'price_yearly' => 0, 'currency' => 'USD', 'cpu_cores' => 4, 'ram_mb' => 8192, 'disk_gb' => 200, 'contabo_product_id' => 'V45', 'is_active' => true, 'is_visible' => true, ]); $response = $this->actingAs($user)->postJson(route('hosting.servers.store'), [ 'product_id' => $product->id, 'server_name' => 'pricing-test-server', 'billing_cycle' => 'monthly', 'region' => 'EU', 'image' => 'ubuntu-image', 'license' => 'none', 'application' => 'none', 'default_user' => 'root', 'additional_ip' => 'none', 'private_networking' => 'disabled', 'storage_type' => 'included', 'object_storage' => 'none', 'server_password' => 'Secure123!!', 'server_password_confirmation' => 'Secure123!!', ]); $response->assertOk(); $order = CustomerHostingOrder::query()->firstOrFail(); $this->assertSame('GHS', $order->currency); $this->assertSame('140.00', number_format((float) $order->amount_paid, 2, '.', '')); } public function test_server_orders_persist_selected_options_password_and_semiannual_term_pricing(): void { $user = User::factory()->create(); $this->mockContaboImages(); $this->mock(ExchangeRateService::class, function ($mock): void { $mock->shouldReceive('getUsdToGhs')->andReturn(15.50); }); $product = HostingProduct::create([ 'name' => 'VPS S', 'slug' => 'vps-s-semiannual-test', 'description' => 'Dynamic VPS order test', 'category' => 'vps', 'type' => 'vps', 'price_monthly' => 0, 'price_quarterly' => 0, 'price_yearly' => 0, 'currency' => 'USD', 'cpu_cores' => 4, 'ram_mb' => 8192, 'disk_gb' => 200, 'contabo_product_id' => 'V45', 'is_active' => true, 'is_visible' => true, ]); $response = $this->actingAs($user)->postJson(route('hosting.servers.store'), [ 'product_id' => $product->id, 'server_name' => 'semiannual-configured-server', 'billing_cycle' => 'semiannual', 'region' => 'EU', 'image' => 'win-image', 'license' => 'plesk_windows', 'application' => 'none', 'default_user' => 'administrator', 'additional_ip' => 'none', 'private_networking' => 'disabled', 'storage_type' => 'included', 'object_storage' => 'eu_250', 'server_password' => 'Abc123!!', 'server_password_confirmation' => 'Abc123!!', ]); $response->assertOk(); $order = CustomerHostingOrder::query()->firstOrFail(); $this->assertSame('semiannual', $order->billing_cycle); $this->assertSame('4985.36', number_format((float) $order->amount_paid, 2, '.', '')); $this->assertSame('win-image', data_get($order->meta, 'server_order.selection.image')); $this->assertSame('administrator', data_get($order->meta, 'server_order.selection.default_user')); $this->assertSame( ['250 GB Object Storage (EU)'], data_get($order->meta, 'server_order.quote.manual_follow_up') ); $this->assertSame('Abc123!!', decrypt((string) data_get($order->meta, 'server_order.server_password_encrypted'))); $this->assertSame('Windows Server 2022', data_get($order->meta, 'server_order.selection_summary.0.value')); } public function test_server_orders_allow_install_later_with_remote_login_only(): void { $user = User::factory()->create(); $this->mockContaboImages(); $this->mock(ExchangeRateService::class, function ($mock): void { $mock->shouldReceive('getUsdToGhs')->andReturn(15.50); }); $product = HostingProduct::create([ 'name' => 'VPS S', 'slug' => 'vps-s-install-later-order-test', 'description' => 'Dynamic VPS order test', 'category' => 'vps', 'type' => 'vps', 'price_monthly' => 0, 'price_quarterly' => 0, 'price_yearly' => 0, 'currency' => 'USD', 'cpu_cores' => 4, 'ram_mb' => 8192, 'disk_gb' => 200, 'contabo_product_id' => 'V45', 'is_active' => true, 'is_visible' => true, ]); $response = $this->actingAs($user)->postJson(route('hosting.servers.store'), [ 'product_id' => $product->id, 'server_name' => 'install-later-server', 'billing_cycle' => 'monthly', 'region' => 'EU', 'image' => '__install_later__', 'license' => 'none', 'application' => 'none', 'default_user' => 'root', 'additional_ip' => 'none', 'private_networking' => 'disabled', 'storage_type' => 'included', 'object_storage' => 'none', 'server_password' => 'Secure123!!', 'server_password_confirmation' => 'Secure123!!', ]); $response->assertOk(); $order = CustomerHostingOrder::query()->firstOrFail(); $this->assertSame('__install_later__', data_get($order->meta, 'server_order.selection.image')); $this->assertSame('none', data_get($order->meta, 'server_order.selection.license')); $this->assertSame('none', data_get($order->meta, 'server_order.selection.application')); $this->assertSame('140.00', number_format((float) $order->amount_paid, 2, '.', '')); } public function test_vps_10_monthly_orders_include_one_time_setup_fee_but_yearly_orders_do_not(): void { $user = User::factory()->create(); $this->mockContaboImages(); $this->mock(ExchangeRateService::class, function ($mock): void { $mock->shouldReceive('getUsdToGhs')->andReturn(15.50); }); $product = HostingProduct::create([ 'name' => 'VPS 10', 'slug' => 'vps-10-setup-fee-test', 'description' => 'Dynamic VPS setup fee test', 'category' => 'vps', 'type' => 'vps', 'price_monthly' => 0, 'price_quarterly' => 0, 'price_yearly' => 0, 'currency' => 'GHS', 'cpu_cores' => 3, 'ram_mb' => 8192, 'disk_gb' => 75, 'contabo_product_id' => 'V91', 'is_active' => true, 'is_visible' => true, ]); $this->actingAs($user)->postJson(route('hosting.servers.store'), [ 'product_id' => $product->id, 'server_name' => 'monthly-setup-fee-server', 'billing_cycle' => 'monthly', 'region' => 'EU', 'image' => '__install_later__', 'license' => 'none', 'application' => 'none', 'default_user' => 'root', 'additional_ip' => 'none', 'private_networking' => 'disabled', 'storage_type' => 'included', 'object_storage' => 'none', 'server_password' => 'Secure123!!', 'server_password_confirmation' => 'Secure123!!', ])->assertOk(); $monthlyOrder = CustomerHostingOrder::query()->latest('id')->firstOrFail(); $this->assertSame('200.00', number_format((float) $monthlyOrder->amount_paid, 2, '.', '')); $this->assertSame('100.00', number_format((float) collect(data_get($monthlyOrder->meta, 'server_order.quote.line_items', []))->firstWhere('code', 'setup_fee')['amount'], 2, '.', '')); $this->actingAs($user)->postJson(route('hosting.servers.store'), [ 'product_id' => $product->id, 'server_name' => 'yearly-no-setup-fee-server', 'billing_cycle' => 'yearly', 'region' => 'EU', 'image' => '__install_later__', 'license' => 'none', 'application' => 'none', 'default_user' => 'root', 'additional_ip' => 'none', 'private_networking' => 'disabled', 'storage_type' => 'included', 'object_storage' => 'none', 'server_password' => 'Secure123!!', 'server_password_confirmation' => 'Secure123!!', ])->assertOk(); $yearlyOrder = CustomerHostingOrder::query()->latest('id')->firstOrFail(); $this->assertSame('960.00', number_format((float) $yearlyOrder->amount_paid, 2, '.', '')); $this->assertNull(collect(data_get($yearlyOrder->meta, 'server_order.quote.line_items', []))->firstWhere('code', 'setup_fee')); } public function test_provisioning_job_uses_selected_server_options_for_contabo(): void { $user = User::factory()->create(); $this->mock(ExchangeRateService::class, function ($mock): void { $mock->shouldReceive('getUsdToGhs')->andReturn(15.50); }); $provider = \Mockery::mock(ContaboProvider::class); $provider->shouldReceive('getAvailableImages')->once()->andReturn([ [ 'id' => 'win-image', 'name' => 'Windows Server 2022', 'description' => 'Windows', 'os_type' => 'windows', 'standard_image' => true, ], ]); $provider->shouldReceive('createInstance') ->once() ->withArgs(function (array $config): bool { $this->assertSame('V45', $config['product_id']); $this->assertSame('win-image', $config['image_id']); $this->assertSame('US-west', $config['region']); $this->assertSame('administrator', $config['default_user']); $this->assertSame('PleskHost', $config['license']); $this->assertSame(6, $config['period']); $this->assertSame('Abc123!!', $config['root_password']); $this->assertArrayHasKey('additionalIps', $config['add_ons']); $this->assertArrayHasKey('privateNetworking', $config['add_ons']); return true; }) ->andReturn([ 'instance_id' => 'instance-123', 'ip_address' => '203.0.113.10', ]); $this->app->instance(ContaboProvider::class, $provider); $product = HostingProduct::create([ 'name' => 'VPS S', 'slug' => 'vps-s-provisioning-test', 'description' => 'Dynamic VPS provisioning test', 'category' => 'vps', 'type' => 'vps', 'price_monthly' => 0, 'price_quarterly' => 0, 'price_yearly' => 0, 'currency' => 'USD', 'cpu_cores' => 4, 'ram_mb' => 8192, 'disk_gb' => 200, 'contabo_product_id' => 'V45', 'contabo_region' => 'EU', 'is_active' => true, 'is_visible' => true, ]); $order = CustomerHostingOrder::create([ 'user_id' => $user->id, 'hosting_product_id' => $product->id, 'domain_name' => 'job-provisioned-server', 'billing_cycle' => 'semiannual', 'amount_paid' => 0, 'currency' => 'GHS', 'status' => CustomerHostingOrder::STATUS_APPROVED, 'meta' => [ 'region' => 'US-west', 'server_order' => [ 'selection' => [ 'image' => 'win-image', 'region' => 'US-west', 'license' => 'plesk_host', 'application' => 'none', 'default_user' => 'administrator', 'additional_ip' => 'one_extra', 'private_networking' => 'enabled', 'storage_type' => 'included', 'object_storage' => 'none', ], 'server_password_encrypted' => encrypt('Abc123!!'), ], ], ]); $capacityService = \Mockery::mock(NodeCapacityService::class); $sharedProvider = \Mockery::mock(SharedNodeProvider::class); $serverOrders = $this->app->make(ServerOrderService::class); $bootstrap = \Mockery::mock(ServerAgentBootstrapService::class); $bootstrap->shouldReceive('ensureBootstrap')->andReturn(['token' => 'bootstrap-token']); $installer = \Mockery::mock(ServerAgentInstallerService::class); $installer->shouldReceive('augmentProvisioningConfig')->andReturnUsing( fn ($freshOrder, array $config, array $bootstrapConfig): array => $config ); (new ProvisionHostingOrderJob($order))->handle( $capacityService, app(HostingResourcePolicyService::class), $sharedProvider, $provider, $serverOrders, $bootstrap, $installer, ); $order->refresh(); $this->assertSame(CustomerHostingOrder::STATUS_ACTIVE, $order->status); $this->assertSame('203.0.113.10', $order->server_ip); $this->assertSame('administrator', $order->server_username); $this->assertSame('instance-123', data_get($order->meta, 'contabo_instance_id')); $this->assertDatabaseHas('provisioning_queue', [ 'customer_hosting_order_id' => $order->id, 'status' => ProvisioningQueueItem::STATUS_COMPLETED, ]); } public function test_ladill_control_panel_selection_sets_internal_panel_url_after_provisioning(): void { $user = User::factory()->create(); $this->mock(ExchangeRateService::class, function ($mock): void { $mock->shouldReceive('getUsdToGhs')->andReturn(15.50); }); $provider = \Mockery::mock(ContaboProvider::class); $provider->shouldReceive('getAvailableImages')->once()->andReturn([ [ 'id' => 'ubuntu-image', 'name' => 'Ubuntu 22.04', 'description' => 'Ubuntu', 'os_type' => 'linux', 'standard_image' => true, ], ]); $provider->shouldReceive('createInstance') ->once() ->withArgs(function (array $config): bool { $this->assertNull($config['license']); $this->assertSame('ladill', $config['panel']); return true; }) ->andReturn([ 'instance_id' => 'instance-panel-123', 'ip_address' => '203.0.113.20', 'status' => 'running', 'region' => 'EU', 'datacenter' => 'MUC1', 'image' => 'ubuntu-image', ]); $this->app->instance(ContaboProvider::class, $provider); $product = HostingProduct::create([ 'name' => 'VPS S', 'slug' => 'vps-s-ladill-panel-test', 'description' => 'Dynamic VPS provisioning test', 'category' => 'vps', 'type' => 'vps', 'price_monthly' => 0, 'price_quarterly' => 0, 'price_yearly' => 0, 'currency' => 'USD', 'cpu_cores' => 4, 'ram_mb' => 8192, 'disk_gb' => 200, 'contabo_product_id' => 'V45', 'contabo_region' => 'EU', 'is_active' => true, 'is_visible' => true, ]); $order = CustomerHostingOrder::create([ 'user_id' => $user->id, 'hosting_product_id' => $product->id, 'domain_name' => 'ladill-panel-server', 'billing_cycle' => 'monthly', 'amount_paid' => 0, 'currency' => 'GHS', 'status' => CustomerHostingOrder::STATUS_APPROVED, 'meta' => [ 'region' => 'EU', 'server_order' => [ 'selection' => [ 'image' => 'ubuntu-image', 'region' => 'EU', 'license' => 'ladill_panel', 'application' => 'none', 'default_user' => 'root', 'additional_ip' => 'none', 'private_networking' => 'disabled', 'storage_type' => 'included', 'object_storage' => 'none', ], 'selection_summary' => [ ['label' => 'License', 'value' => 'Ladill Control Panel'], ], 'server_password_encrypted' => encrypt('Root123!!'), ], ], ]); $capacityService = \Mockery::mock(NodeCapacityService::class); $sharedProvider = \Mockery::mock(SharedNodeProvider::class); $serverOrders = $this->app->make(ServerOrderService::class); $bootstrap = \Mockery::mock(ServerAgentBootstrapService::class); $bootstrap->shouldReceive('ensureBootstrap')->andReturn(['token' => 'bootstrap-token']); $installer = \Mockery::mock(ServerAgentInstallerService::class); $installer->shouldReceive('augmentProvisioningConfig')->andReturnUsing( fn ($freshOrder, array $config, array $bootstrapConfig): array => $config ); (new ProvisionHostingOrderJob($order))->handle( $capacityService, app(HostingResourcePolicyService::class), $sharedProvider, $provider, $serverOrders, $bootstrap, $installer, ); $order->refresh(); $this->assertSame(route('hosting.server-panel.show', $order), $order->control_panel_url); $this->assertSame('ladill', data_get($order->meta, 'server_panel.panel')); $this->assertSame('instance-panel-123', data_get($order->meta, 'contabo_instance_id')); } public function test_ladill_server_panel_page_and_power_action_work_for_selected_orders(): void { $user = User::factory()->create(); $product = HostingProduct::create([ 'name' => 'VPS S', 'slug' => 'vps-s-panel-route-test', 'description' => 'Dynamic VPS provisioning test', 'category' => 'vps', 'type' => 'vps', 'price_monthly' => 0, 'price_quarterly' => 0, 'price_yearly' => 0, 'currency' => 'USD', 'cpu_cores' => 4, 'ram_mb' => 8192, 'disk_gb' => 200, 'contabo_product_id' => 'V45', 'is_active' => true, 'is_visible' => true, ]); $order = CustomerHostingOrder::create([ 'user_id' => $user->id, 'hosting_product_id' => $product->id, 'domain_name' => 'panel-route-server', 'billing_cycle' => 'monthly', 'amount_paid' => 0, 'currency' => 'GHS', 'status' => CustomerHostingOrder::STATUS_ACTIVE, 'server_ip' => '203.0.113.50', 'server_username' => 'root', 'control_panel_url' => 'http://localhost/hosting/orders/1/panel', 'meta' => [ 'contabo_instance_id' => 'instance-route-123', 'server_order' => [ 'selection' => [ 'license' => 'ladill_panel', 'application' => 'none', ], 'selection_summary' => [ ['label' => 'License', 'value' => 'Ladill Control Panel'], ], ], 'server_panel' => [ 'panel' => 'ladill', 'power_status' => 'on', 'instance_status' => 'running', ], ], ]); $provider = \Mockery::mock(ContaboProvider::class); $provider->shouldReceive('startInstance')->once()->with('instance-route-123')->andReturn(true); $this->app->instance(ContaboProvider::class, $provider); $this->actingAs($user) ->get(route('hosting.server-panel.show', $order)) ->assertOk() ->assertSee('Ladill Control Panel') ->assertSee('panel-route-server'); $this->actingAs($user) ->post(route('hosting.server-panel.power', [$order, 'start'])) ->assertRedirect(); } public function test_provisioning_uses_cloud_init_for_webmin_profile(): void { $user = User::factory()->create(); $this->mock(ExchangeRateService::class, function ($mock): void { $mock->shouldReceive('getUsdToGhs')->andReturn(15.50); }); $provider = \Mockery::mock(ContaboProvider::class); $provider->shouldReceive('getAvailableImages')->once()->andReturn([ [ 'id' => 'ubuntu-image', 'name' => 'Ubuntu 22.04', 'description' => 'Ubuntu', 'os_type' => 'linux', 'standard_image' => true, ], ]); $provider->shouldReceive('generateCloudInit')->once()->andReturn('#cloud-config webmin'); $provider->shouldReceive('createInstance') ->once() ->withArgs(function (array $config): bool { $this->assertSame('ubuntu-image', $config['image_id']); $this->assertSame('#cloud-config webmin', $config['user_data']); $this->assertNull($config['license']); $this->assertNull($config['application_id']); return true; }) ->andReturn([ 'instance_id' => 'instance-webmin-123', 'ip_address' => '203.0.113.30', ]); $this->app->instance(ContaboProvider::class, $provider); $product = HostingProduct::create([ 'name' => 'VPS S', 'slug' => 'vps-s-webmin-test', 'description' => 'Dynamic VPS provisioning test', 'category' => 'vps', 'type' => 'vps', 'price_monthly' => 0, 'price_quarterly' => 0, 'price_yearly' => 0, 'currency' => 'USD', 'cpu_cores' => 4, 'ram_mb' => 8192, 'disk_gb' => 200, 'contabo_product_id' => 'V45', 'contabo_region' => 'EU', 'is_active' => true, 'is_visible' => true, ]); $order = CustomerHostingOrder::create([ 'user_id' => $user->id, 'hosting_product_id' => $product->id, 'domain_name' => 'webmin-server', 'billing_cycle' => 'monthly', 'amount_paid' => 0, 'currency' => 'GHS', 'status' => CustomerHostingOrder::STATUS_APPROVED, 'meta' => [ 'region' => 'EU', 'server_order' => [ 'selection' => [ 'image' => 'ubuntu-image', 'region' => 'EU', 'license' => 'none', 'application' => 'webmin', 'default_user' => 'root', 'additional_ip' => 'none', 'private_networking' => 'disabled', 'storage_type' => 'included', 'object_storage' => 'none', ], 'server_password_encrypted' => encrypt('Root123!!'), ], ], ]); $capacityService = \Mockery::mock(NodeCapacityService::class); $sharedProvider = \Mockery::mock(SharedNodeProvider::class); $serverOrders = $this->app->make(ServerOrderService::class); $bootstrap = \Mockery::mock(ServerAgentBootstrapService::class); $bootstrap->shouldReceive('ensureBootstrap')->andReturn(['token' => 'bootstrap-token']); $installer = \Mockery::mock(ServerAgentInstallerService::class); $installer->shouldReceive('augmentProvisioningConfig')->andReturnUsing( fn ($freshOrder, array $config, array $bootstrapConfig): array => $config ); (new ProvisionHostingOrderJob($order))->handle( $capacityService, app(HostingResourcePolicyService::class), $sharedProvider, $provider, $serverOrders, $bootstrap, $installer, ); $this->assertDatabaseHas('provisioning_queue', [ 'customer_hosting_order_id' => $order->id, 'status' => ProvisioningQueueItem::STATUS_COMPLETED, ]); } }