fix(watchsync): close connection validation gaps
This commit is contained in:
@@ -8,6 +8,7 @@ import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
@@ -774,17 +775,24 @@ func validateRequiredConnectionSchemaIsRenderable(schema *pluginv1.ConfigSchema)
|
||||
}
|
||||
}
|
||||
for key, property := range document.Properties {
|
||||
field := explicit[key]
|
||||
switch property.Type {
|
||||
case "string", watchSyncJSONSchemaNumberType, "integer", "boolean":
|
||||
continue
|
||||
case "array":
|
||||
field := explicit[key]
|
||||
if field != nil && field.GetControl() == pluginv1.AdminFormControl_ADMIN_FORM_CONTROL_MULTI_SELECT &&
|
||||
(property.Items == nil || property.Items.Type == "string" || property.Items.Type == watchSyncJSONSchemaNumberType ||
|
||||
property.Items.Type == "integer" || property.Items.Type == "boolean") {
|
||||
continue
|
||||
}
|
||||
default:
|
||||
// A property whose shape comes from enum/const/$ref cannot be
|
||||
// inferred from type alone, but an explicit scalar form control is
|
||||
// still a complete input mechanism. Direct object properties remain
|
||||
// unsupported because none of these controls produces an object.
|
||||
if property.Type != "object" && connectionAdminFieldRendersValue(field) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
return fmt.Errorf(
|
||||
"required connection config %q property %q needs a renderable admin_form field because type %q cannot be inferred",
|
||||
@@ -796,6 +804,24 @@ func validateRequiredConnectionSchemaIsRenderable(schema *pluginv1.ConfigSchema)
|
||||
return nil
|
||||
}
|
||||
|
||||
func connectionAdminFieldRendersValue(field *pluginv1.AdminFormField) bool {
|
||||
if field == nil {
|
||||
return false
|
||||
}
|
||||
switch field.GetControl() {
|
||||
case pluginv1.AdminFormControl_ADMIN_FORM_CONTROL_TEXT,
|
||||
pluginv1.AdminFormControl_ADMIN_FORM_CONTROL_TEXTAREA,
|
||||
pluginv1.AdminFormControl_ADMIN_FORM_CONTROL_PASSWORD,
|
||||
pluginv1.AdminFormControl_ADMIN_FORM_CONTROL_NUMBER,
|
||||
pluginv1.AdminFormControl_ADMIN_FORM_CONTROL_SWITCH,
|
||||
pluginv1.AdminFormControl_ADMIN_FORM_CONTROL_SELECT,
|
||||
pluginv1.AdminFormControl_ADMIN_FORM_CONTROL_MULTI_SELECT:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func validateConnectionAdminFormValue(schema *pluginv1.ConfigSchema, value map[string]any) error {
|
||||
if schema == nil || schema.GetAdminForm() == nil {
|
||||
return nil
|
||||
@@ -868,6 +894,9 @@ func connectionConfigNumber(value any) (float64, bool) {
|
||||
case json.Number:
|
||||
number, err := typed.Float64()
|
||||
return number, err == nil
|
||||
case string:
|
||||
number, err := strconv.ParseFloat(strings.TrimSpace(typed), 64)
|
||||
return number, err == nil
|
||||
default:
|
||||
return 0, false
|
||||
}
|
||||
|
||||
@@ -398,6 +398,9 @@ func TestPluginProviderRejectsRequiredConnectionConfigTheWebCannotRender(t *test
|
||||
ConnectionConfigSchema: []*pluginv1.ConfigSchema{{
|
||||
Key: "server", Required: true,
|
||||
JsonSchema: `{"type":"object","properties":{"headers":{"type":"object"}}}`,
|
||||
AdminForm: &pluginv1.AdminFormDescriptor{Fields: []*pluginv1.AdminFormField{{
|
||||
Key: "headers", Control: pluginv1.AdminFormControl_ADMIN_FORM_CONTROL_TEXTAREA,
|
||||
}}},
|
||||
}},
|
||||
ResolveClient: func(context.Context, int, string) (WatchSyncPluginClient, error) {
|
||||
return &fakeWatchSyncPluginClient{}, nil
|
||||
@@ -408,7 +411,7 @@ func TestPluginProviderRejectsRequiredConnectionConfigTheWebCannotRender(t *test
|
||||
}
|
||||
}
|
||||
|
||||
func TestPluginProviderAcceptsRequiredScalarFieldsMissingFromPartialAdminForm(t *testing.T) {
|
||||
func TestPluginProviderAcceptsRenderableRequiredConnectionConfig(t *testing.T) {
|
||||
_, err := NewPluginProvider(PluginProviderOptions{
|
||||
InstallationID: 4, ProviderKey: testPluginProviderKey, CapabilityID: testPluginCapabilityID,
|
||||
Descriptor: &pluginv1.WatchSyncProviderDescriptor{AuthMethods: []pluginv1.WatchSyncAuthMethod{
|
||||
@@ -430,6 +433,21 @@ func TestPluginProviderAcceptsRequiredScalarFieldsMissingFromPartialAdminForm(t
|
||||
Options: []*pluginv1.AdminFormOption{{Value: "true", Label: "Enabled"}, {Value: "false", Label: "Disabled"}},
|
||||
}}},
|
||||
},
|
||||
{
|
||||
Key: "mode", Required: true,
|
||||
JsonSchema: `{"type":"object","properties":{"value":{"enum":["standard","anime"]}}}`,
|
||||
AdminForm: &pluginv1.AdminFormDescriptor{Fields: []*pluginv1.AdminFormField{{
|
||||
Key: "value", Control: pluginv1.AdminFormControl_ADMIN_FORM_CONTROL_SELECT,
|
||||
Options: []*pluginv1.AdminFormOption{{Value: "standard", Label: "Standard"}, {Value: "anime", Label: "Anime"}},
|
||||
}}},
|
||||
},
|
||||
{
|
||||
Key: "reference", Required: true,
|
||||
JsonSchema: `{"type":"object","properties":{"endpoint":{"$ref":"#/$defs/endpoint"}},"$defs":{"endpoint":{"type":"string","format":"uri"}}}`,
|
||||
AdminForm: &pluginv1.AdminFormDescriptor{Fields: []*pluginv1.AdminFormField{{
|
||||
Key: "endpoint", Control: pluginv1.AdminFormControl_ADMIN_FORM_CONTROL_TEXT,
|
||||
}}},
|
||||
},
|
||||
},
|
||||
ResolveClient: func(context.Context, int, string) (WatchSyncPluginClient, error) {
|
||||
return &fakeWatchSyncPluginClient{}, nil
|
||||
@@ -452,7 +470,7 @@ func TestPluginProviderEnforcesConnectionAdminFormValidation(t *testing.T) {
|
||||
}},
|
||||
ConnectionConfigSchema: []*pluginv1.ConfigSchema{{
|
||||
Key: "server", Required: true,
|
||||
JsonSchema: `{"type":"object","properties":{"name":{"type":"string"},"port":{"type":"number"},"password":{"type":"string","format":"password"}},"required":["name","port","password"]}`,
|
||||
JsonSchema: `{"type":"object","properties":{"name":{"type":"string"},"port":{},"password":{"type":"string","format":"password"}},"required":["name","port","password"]}`,
|
||||
AdminForm: &pluginv1.AdminFormDescriptor{Fields: []*pluginv1.AdminFormField{
|
||||
{Key: "name", Control: pluginv1.AdminFormControl_ADMIN_FORM_CONTROL_TEXT, Validation: &pluginv1.AdminFormValidation{Pattern: `^[a-z]+$`, MinLength: 3, MaxLength: 8}},
|
||||
{Key: "port", Control: pluginv1.AdminFormControl_ADMIN_FORM_CONTROL_NUMBER, Validation: &pluginv1.AdminFormValidation{HasMin: true, Min: 1, HasMax: true, Max: 65535}},
|
||||
@@ -474,6 +492,7 @@ func TestPluginProviderEnforcesConnectionAdminFormValidation(t *testing.T) {
|
||||
}{
|
||||
{name: "pattern", values: map[string]any{"name": "Bad", "port": 443.0, "password": "long-enough"}, message: "is invalid"},
|
||||
{name: "number", values: map[string]any{"name": "good", "port": 70000.0, "password": "long-enough"}, message: "at most 65535"},
|
||||
{name: "numeric string", values: map[string]any{"name": "good", "port": "70000", "password": "long-enough"}, message: "at most 65535"},
|
||||
{name: "secret length", values: map[string]any{"name": "good", "port": 443.0, "password": "leaky"}, message: "at least 8 characters"},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
|
||||
Reference in New Issue
Block a user