feat: harden runtime import and frontend verification workflows
This commit is contained in:
@@ -99,6 +99,7 @@ func (r AccountImportResult) HasAdvisoryWarning() bool {
|
||||
type hostAdapter interface {
|
||||
sub2api.HostAdapter
|
||||
CheckGatewayAccess(ctx context.Context, req sub2api.GatewayAccessCheckRequest) (sub2api.GatewayAccessResult, error)
|
||||
CheckGatewayCompletion(ctx context.Context, req sub2api.GatewayCompletionCheckRequest) (sub2api.GatewayCompletionResult, error)
|
||||
}
|
||||
|
||||
func GatewayAccessReady(result sub2api.GatewayAccessResult) bool {
|
||||
|
||||
@@ -911,7 +911,15 @@ func (f *fakeHostAdapter) GetHostVersion(context.Context) (string, error) {
|
||||
return f.hostVersion, nil
|
||||
}
|
||||
func (f *fakeHostAdapter) ProbeCapabilities(context.Context) (sub2api.HostCapabilities, error) {
|
||||
return sub2api.HostCapabilities{}, nil
|
||||
return sub2api.HostCapabilities{
|
||||
Groups: true,
|
||||
Channels: true,
|
||||
Plans: true,
|
||||
Accounts: true,
|
||||
AccountTest: true,
|
||||
AccountModels: true,
|
||||
Subscriptions: true,
|
||||
}, nil
|
||||
}
|
||||
func (f *fakeHostAdapter) CreateGroup(_ context.Context, req sub2api.CreateGroupRequest) (sub2api.GroupRef, error) {
|
||||
f.createGroupCalls++
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"sub2api-cn-relay-manager/internal/host/sub2api"
|
||||
"sub2api-cn-relay-manager/internal/pack"
|
||||
"sub2api-cn-relay-manager/internal/store/sqlite"
|
||||
)
|
||||
@@ -66,6 +67,12 @@ func (s *RuntimeImportService) Import(ctx context.Context, req RuntimeImportRequ
|
||||
if err != nil {
|
||||
return RuntimeImportResult{}, fmt.Errorf("probe host capabilities: %w", err)
|
||||
}
|
||||
|
||||
// Host readiness preflight check
|
||||
if err := validateHostReadiness(capabilities); err != nil {
|
||||
return RuntimeImportResult{}, fmt.Errorf("host readiness preflight failed: %w", err)
|
||||
}
|
||||
|
||||
capabilityProbeJSON, err := json.Marshal(capabilities)
|
||||
if err != nil {
|
||||
return RuntimeImportResult{}, fmt.Errorf("marshal host capabilities: %w", err)
|
||||
@@ -302,3 +309,26 @@ func firstNonEmpty(values ...string) string {
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// validateHostReadiness performs preflight checks on host capabilities
|
||||
// to ensure the host is ready for import operations.
|
||||
func validateHostReadiness(caps sub2api.HostCapabilities) error {
|
||||
var missing []string
|
||||
if !caps.Groups {
|
||||
missing = append(missing, "groups")
|
||||
}
|
||||
if !caps.Channels {
|
||||
missing = append(missing, "channels")
|
||||
}
|
||||
if !caps.Accounts {
|
||||
missing = append(missing, "accounts")
|
||||
}
|
||||
if !caps.AccountTest {
|
||||
missing = append(missing, "account_test")
|
||||
}
|
||||
|
||||
if len(missing) > 0 {
|
||||
return fmt.Errorf("host missing required capabilities: %v", missing)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -806,3 +806,85 @@ func queryCount(t *testing.T, db *sql.DB, table string) int {
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
func TestValidateHostReadiness(t *testing.T) {
|
||||
t.Run("all capabilities present", func(t *testing.T) {
|
||||
caps := sub2api.HostCapabilities{
|
||||
Groups: true,
|
||||
Channels: true,
|
||||
Accounts: true,
|
||||
AccountTest: true,
|
||||
AccountModels: true,
|
||||
Plans: true,
|
||||
Subscriptions: true,
|
||||
}
|
||||
if err := validateHostReadiness(caps); err != nil {
|
||||
t.Fatalf("validateHostReadiness() = %v, want nil", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("missing groups", func(t *testing.T) {
|
||||
caps := sub2api.HostCapabilities{
|
||||
Groups: false,
|
||||
Channels: true,
|
||||
Accounts: true,
|
||||
AccountTest: true,
|
||||
}
|
||||
if err := validateHostReadiness(caps); err == nil {
|
||||
t.Fatal("validateHostReadiness() = nil, want error for missing groups")
|
||||
} else if !strings.Contains(err.Error(), "groups") {
|
||||
t.Fatalf("validateHostReadiness() = %v, want error mentioning groups", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("missing multiple capabilities", func(t *testing.T) {
|
||||
caps := sub2api.HostCapabilities{
|
||||
Groups: false,
|
||||
Channels: false,
|
||||
Accounts: false,
|
||||
AccountTest: false,
|
||||
}
|
||||
if err := validateHostReadiness(caps); err == nil {
|
||||
t.Fatal("validateHostReadiness() = nil, want error for multiple missing capabilities")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("missing accounts", func(t *testing.T) {
|
||||
caps := sub2api.HostCapabilities{
|
||||
Groups: true,
|
||||
Channels: true,
|
||||
Accounts: false,
|
||||
AccountTest: true,
|
||||
}
|
||||
if err := validateHostReadiness(caps); err == nil {
|
||||
t.Fatal("validateHostReadiness() = nil, want error for missing accounts")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("missing test account", func(t *testing.T) {
|
||||
caps := sub2api.HostCapabilities{
|
||||
Groups: true,
|
||||
Channels: true,
|
||||
Accounts: true,
|
||||
AccountTest: false,
|
||||
}
|
||||
if err := validateHostReadiness(caps); err == nil {
|
||||
t.Fatal("validateHostReadiness() = nil, want error for missing account_test")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("plans and subscriptions not required", func(t *testing.T) {
|
||||
caps := sub2api.HostCapabilities{
|
||||
Groups: true,
|
||||
Channels: true,
|
||||
Accounts: true,
|
||||
AccountTest: true,
|
||||
AccountModels: false,
|
||||
Plans: false,
|
||||
Subscriptions: false,
|
||||
}
|
||||
if err := validateHostReadiness(caps); err != nil {
|
||||
t.Fatalf("validateHostReadiness() = %v, want nil (plans/subscriptions are optional)", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user