Files
compute/object/provider_util_test.go
zeekay 188765c508 fix(nodepool): propagate DO delete errors; widen active-provider predicate
BUG 1 (delete orphans DO resources): DeleteNodePool swallowed the DigitalOcean
delete error (`_ = client.DeleteNodePool(...)`) and dropped the DB row
unconditionally, so a 422 returned while a pool is still provisioning left the
real pool + droplets running and billing while the API reported success. Route
delete through object.DeleteNodePoolCloud, which confirms the provider deleted
the pool (a 404 is treated as already-gone) before removing the DB row; a 422 or
any other error is propagated so the caller retries and the row is preserved.

BUG 2 (reconcile cannot see the live provider): getActiveCloudProviders required
ClientSecret != "" and Category in {Public Cloud, Private Cloud}, so the live
prod DO row (token in ClientId, ClientSecret empty, Category "Cloud") was
filtered out and SyncNodePoolsCloud / GET /v1/get-node-pools returned []. Widen
the predicate in one place (isActiveCloudProvider): accept the token in ClientId
OR ClientSecret and accept Category "Cloud". Also fixes getMachinesCloud, which
shares the predicate.

Tests: TestConfirmCloudPoolDeleted (ok/404/422/500/opaque + wrapped errors) and
TestIsActiveCloudProvider (prod DO shape, legacy shapes, rejections).
2026-07-02 21:20:11 -07:00

44 lines
2.1 KiB
Go

// Copyright 2025 Hanzo Industries Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package object
import "testing"
// TestIsActiveCloudProvider pins the widened predicate: the live prod DO row
// carries its token in ClientId (ClientSecret empty) with Category "Cloud", and
// must be accepted alongside the legacy Public/Private Cloud shapes.
func TestIsActiveCloudProvider(t *testing.T) {
cases := []struct {
name string
p *Provider
want bool
}{
{"prod DO row: token in ClientId, Category Cloud", &Provider{ClientId: "dop_v1_token", Category: "Cloud", State: "Active"}, true},
{"token in ClientSecret, Public Cloud", &Provider{ClientSecret: "secret", Category: "Public Cloud", State: "Active"}, true},
{"token in ClientId, Private Cloud", &Provider{ClientId: "token", Category: "Private Cloud", State: "Active"}, true},
{"both tokens set, Cloud", &Provider{ClientId: "a", ClientSecret: "b", Category: "Cloud", State: "Active"}, true},
{"no token rejected", &Provider{Category: "Cloud", State: "Active"}, false},
{"inactive state rejected", &Provider{ClientId: "token", Category: "Cloud", State: "Inactive"}, false},
{"empty state rejected", &Provider{ClientId: "token", Category: "Cloud", State: ""}, false},
{"blockchain category rejected", &Provider{ClientId: "token", Category: "Blockchain", State: "Active"}, false},
{"unknown category rejected", &Provider{ClientId: "token", Category: "Storage", State: "Active"}, false},
}
for _, tc := range cases {
if got := isActiveCloudProvider(tc.p); got != tc.want {
t.Errorf("%s: isActiveCloudProvider = %v, want %v", tc.name, got, tc.want)
}
}
}