feat(dedicated): bandwidth + private networking restructure
Bandwidth: dropped the "10 Gbps unmetered (fair-use)" tier — at our \$295/mo it was a margin trap (Evocative bills 95th percentile at \$0.48/Mbps; saturated 10G = \~\$4,560/mo cost). Replaced with a "10 Gbps + 500 TB" tier at \$345/mo. 500 TB covers 99% of legitimate heavy use; abuse customers self-select into a metered package or get billed for overage. New bandwidth ladder: 1 Gbps unmetered (baseline) \$0 10 Gbps + 10 TB \$45 10 Gbps + 50 TB \$95 10 Gbps + 100 TB \$175 10 Gbps + 500 TB (NEW, replaces ∞) \$345 Private Networking: new group, separate from public Bandwidth. Customer can pick a private intra-rack link speed independent of their public uplink. Traffic stays on our internal switch fabric and isn't metered. Flat monthly per the brainstorm decision (no setup fees). 1 Gbit private (included, default) \$0 10 Gbit private \$25 40 Gbit private \$75 Attached to all 8 14th-gen plans. Updated test count: now 10 Dedicated 14th Gen config groups (was 9). 14/14 tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -476,7 +476,7 @@ class ConfigOptionSeeder extends Seeder
|
|||||||
['label' => '10 Gbps + 10 TB included', 'value' => '10g-10tb', 'monthly' => 45.00],
|
['label' => '10 Gbps + 10 TB included', 'value' => '10g-10tb', 'monthly' => 45.00],
|
||||||
['label' => '10 Gbps + 50 TB included', 'value' => '10g-50tb', 'monthly' => 95.00],
|
['label' => '10 Gbps + 50 TB included', 'value' => '10g-50tb', 'monthly' => 95.00],
|
||||||
['label' => '10 Gbps + 100 TB included', 'value' => '10g-100tb', 'monthly' => 175.00],
|
['label' => '10 Gbps + 100 TB included', 'value' => '10g-100tb', 'monthly' => 175.00],
|
||||||
['label' => '10 Gbps unmetered (fair-use AUP)', 'value' => '10g-unmetered-fair-use', 'monthly' => 295.00],
|
['label' => '10 Gbps + 500 TB included', 'value' => '10g-500tb', 'monthly' => 345.00],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$gen14Bandwidth->plans()->syncWithoutDetaching($gen14AllPlans);
|
$gen14Bandwidth->plans()->syncWithoutDetaching($gen14AllPlans);
|
||||||
@@ -509,6 +509,30 @@ class ConfigOptionSeeder extends Seeder
|
|||||||
|
|
||||||
$gen14Ipv4->plans()->syncWithoutDetaching($gen14AllPlans);
|
$gen14Ipv4->plans()->syncWithoutDetaching($gen14AllPlans);
|
||||||
|
|
||||||
|
// ─── Dedicated 14th Gen — Private Networking ────────────────────
|
||||||
|
// Separate from public bandwidth. Intra-rack private link with no
|
||||||
|
// bandwidth metering — traffic never traverses the public internet,
|
||||||
|
// so only the link speed itself is billable. Flat monthly, no setup.
|
||||||
|
$gen14PrivateNet = PlanConfigGroup::updateOrCreate(
|
||||||
|
['name' => 'Dedicated 14th Gen — Private Networking'],
|
||||||
|
[
|
||||||
|
'description' => 'Optional dedicated private network link for server-to-server traffic. Unmetered (traffic stays on our internal switch fabric).',
|
||||||
|
'mode' => 'preset',
|
||||||
|
'service_type' => 'dedicated',
|
||||||
|
'is_active' => true,
|
||||||
|
'sort_order' => 46,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
$gen14PrivateNetOption = $this->seedRadioOption($gen14PrivateNet, 'Private Network', false, 1);
|
||||||
|
$this->seedValues($gen14PrivateNetOption, [
|
||||||
|
['label' => '1 Gbit private (included)', 'value' => '1g', 'monthly' => 0, 'is_default' => true],
|
||||||
|
['label' => '10 Gbit private', 'value' => '10g', 'monthly' => 25.00],
|
||||||
|
['label' => '40 Gbit private', 'value' => '40g', 'monthly' => 75.00],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$gen14PrivateNet->plans()->syncWithoutDetaching($gen14AllPlans);
|
||||||
|
|
||||||
// ─── Dedicated 14th Gen — LFF Drive Bays ────────────────────────
|
// ─── Dedicated 14th Gen — LFF Drive Bays ────────────────────────
|
||||||
$gen14Lff = PlanConfigGroup::updateOrCreate(
|
$gen14Lff = PlanConfigGroup::updateOrCreate(
|
||||||
['name' => 'Dedicated 14th Gen — LFF Drive Bays'],
|
['name' => 'Dedicated 14th Gen — LFF Drive Bays'],
|
||||||
|
|||||||
@@ -94,9 +94,11 @@ test('detail page exposes chassis-specific config groups', function (): void {
|
|||||||
$configGroups = collect($response->viewData('page')['props']['configGroups']);
|
$configGroups = collect($response->viewData('page')['props']['configGroups']);
|
||||||
$names = $configGroups->pluck('name');
|
$names = $configGroups->pluck('name');
|
||||||
|
|
||||||
// R740xd variants get the R740xd CPU group, not the standard CPU group
|
// All chassis (including R740xd variants) now use the standard CPU
|
||||||
expect($names)->toContain('Dedicated 14th Gen — CPU Upgrade (R740xd)');
|
// group; the (R740xd) group is detached and the route-level
|
||||||
expect($names)->not->toContain('Dedicated 14th Gen — CPU Upgrade');
|
// cpu_premium filter handles Platinum visibility.
|
||||||
|
expect($names)->toContain('Dedicated 14th Gen — CPU Upgrade');
|
||||||
|
expect($names)->not->toContain('Dedicated 14th Gen — CPU Upgrade (R740xd)');
|
||||||
|
|
||||||
// Universal groups (RAM, OS, Bandwidth, IPv4) attach to all 8 chassis
|
// Universal groups (RAM, OS, Bandwidth, IPv4) attach to all 8 chassis
|
||||||
expect($names)->toContain('Dedicated 14th Gen — RAM Upgrade');
|
expect($names)->toContain('Dedicated 14th Gen — RAM Upgrade');
|
||||||
@@ -105,29 +107,61 @@ test('detail page exposes chassis-specific config groups', function (): void {
|
|||||||
expect($names)->toContain('Dedicated 14th Gen — IPv4 Block');
|
expect($names)->toContain('Dedicated 14th Gen — IPv4 Block');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('non-R740xd chassis get the standard CPU group', function (): void {
|
test('Platinum CPU is filtered out for chassis that do not support it', function (): void {
|
||||||
$response = $this->get('/dedicated-servers/r440-4lff');
|
// R440 and R540 should NOT see Platinum 8280.
|
||||||
$response->assertOk();
|
foreach (['r440-4lff', 'r540-8lff'] as $slug) {
|
||||||
|
$response = $this->get("/dedicated-servers/{$slug}");
|
||||||
|
$cpuGroup = collect($response->viewData('page')['props']['configGroups'])
|
||||||
|
->firstWhere('name', 'Dedicated 14th Gen — CPU Upgrade');
|
||||||
|
$values = collect($cpuGroup['options'][0]['values'])->pluck('value')->all();
|
||||||
|
expect($values)->not->toContain('platinum-8280');
|
||||||
|
}
|
||||||
|
|
||||||
$names = collect($response->viewData('page')['props']['configGroups'])->pluck('name');
|
// R640, R740, R740xd variants SHOULD see Platinum.
|
||||||
|
foreach (['r640-8sff', 'r740-16sff', 'r740xd-24sff', 'r740xd-12lff', 'r640-10nvme', 'r740xd-24nvme'] as $slug) {
|
||||||
expect($names)->toContain('Dedicated 14th Gen — CPU Upgrade');
|
$response = $this->get("/dedicated-servers/{$slug}");
|
||||||
expect($names)->not->toContain('Dedicated 14th Gen — CPU Upgrade (R740xd)');
|
$cpuGroup = collect($response->viewData('page')['props']['configGroups'])
|
||||||
|
->firstWhere('name', 'Dedicated 14th Gen — CPU Upgrade');
|
||||||
|
$values = collect($cpuGroup['options'][0]['values'])->pluck('value')->all();
|
||||||
|
expect($values)->toContain('platinum-8280');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
test('seeder creates the 9 dedicated 14th-gen config groups', function (): void {
|
test('1.5 TB RAM is filtered out for chassis with 16 DIMM slots', function (): void {
|
||||||
|
// R440 and R540 cap at 1 TB.
|
||||||
|
foreach (['r440-4lff', 'r540-8lff'] as $slug) {
|
||||||
|
$response = $this->get("/dedicated-servers/{$slug}");
|
||||||
|
$ramGroup = collect($response->viewData('page')['props']['configGroups'])
|
||||||
|
->firstWhere('name', 'Dedicated 14th Gen — RAM Upgrade');
|
||||||
|
$values = collect($ramGroup['options'][0]['values'])->pluck('value')->all();
|
||||||
|
expect($values)->not->toContain('1536');
|
||||||
|
expect($values)->toContain('1024');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 24-DIMM-slot chassis should see 1.5 TB.
|
||||||
|
foreach (['r640-8sff', 'r740xd-24nvme'] as $slug) {
|
||||||
|
$response = $this->get("/dedicated-servers/{$slug}");
|
||||||
|
$ramGroup = collect($response->viewData('page')['props']['configGroups'])
|
||||||
|
->firstWhere('name', 'Dedicated 14th Gen — RAM Upgrade');
|
||||||
|
$values = collect($ramGroup['options'][0]['values'])->pluck('value')->all();
|
||||||
|
expect($values)->toContain('1536');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('seeder creates the 10 dedicated 14th-gen config groups', function (): void {
|
||||||
$names = PlanConfigGroup::query()
|
$names = PlanConfigGroup::query()
|
||||||
->where('name', 'like', 'Dedicated 14th Gen%')
|
->where('name', 'like', 'Dedicated 14th Gen%')
|
||||||
->pluck('name')
|
->pluck('name')
|
||||||
->all();
|
->all();
|
||||||
|
|
||||||
expect(count($names))->toBe(9);
|
expect(count($names))->toBe(10);
|
||||||
expect($names)->toContain('Dedicated 14th Gen — CPU Upgrade');
|
expect($names)->toContain('Dedicated 14th Gen — CPU Upgrade');
|
||||||
expect($names)->toContain('Dedicated 14th Gen — CPU Upgrade (R740xd)');
|
expect($names)->toContain('Dedicated 14th Gen — CPU Upgrade (R740xd)');
|
||||||
expect($names)->toContain('Dedicated 14th Gen — RAM Upgrade');
|
expect($names)->toContain('Dedicated 14th Gen — RAM Upgrade');
|
||||||
expect($names)->toContain('Dedicated 14th Gen — Operating System');
|
expect($names)->toContain('Dedicated 14th Gen — Operating System');
|
||||||
expect($names)->toContain('Dedicated 14th Gen — Bandwidth');
|
expect($names)->toContain('Dedicated 14th Gen — Bandwidth');
|
||||||
expect($names)->toContain('Dedicated 14th Gen — IPv4 Block');
|
expect($names)->toContain('Dedicated 14th Gen — IPv4 Block');
|
||||||
|
expect($names)->toContain('Dedicated 14th Gen — Private Networking');
|
||||||
expect($names)->toContain('Dedicated 14th Gen — LFF Drive Bays');
|
expect($names)->toContain('Dedicated 14th Gen — LFF Drive Bays');
|
||||||
expect($names)->toContain('Dedicated 14th Gen — SFF Drive Bays');
|
expect($names)->toContain('Dedicated 14th Gen — SFF Drive Bays');
|
||||||
expect($names)->toContain('Dedicated 14th Gen — NVMe Drive Bays');
|
expect($names)->toContain('Dedicated 14th Gen — NVMe Drive Bays');
|
||||||
|
|||||||
Reference in New Issue
Block a user