841 lines
24 KiB
Go
841 lines
24 KiB
Go
package plugins
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
pluginv1 "github.com/Silo-Server/silo-plugin-sdk/pkg/pluginproto/silo/plugin/v1"
|
|
"google.golang.org/protobuf/types/known/structpb"
|
|
)
|
|
|
|
type fakeServiceConfigStore struct {
|
|
configsByInstallation map[int][]*RuntimeConfig
|
|
puts []putGlobalConfigCall
|
|
putErr error
|
|
casFailures int
|
|
casCalls int
|
|
concurrentUpdates map[string]any
|
|
}
|
|
|
|
type putGlobalConfigCall struct {
|
|
installationID int
|
|
key string
|
|
value map[string]any
|
|
}
|
|
|
|
func TestPreserveStoredSecretsKeepsRedactedBlankAndAcceptsReplacement(t *testing.T) {
|
|
store := &fakeServiceConfigStore{configsByInstallation: map[int][]*RuntimeConfig{
|
|
7: {
|
|
{InstallationID: 7, Key: "account", Value: map[string]any{"api_key": "saved", "region": "old"}},
|
|
},
|
|
}}
|
|
service := &Service{configs: store}
|
|
|
|
merged, err := service.preserveStoredSecrets(
|
|
context.Background(),
|
|
7,
|
|
"account",
|
|
map[string]any{"api_key": "", "region": "new"},
|
|
[][]string{{"api_key"}},
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("preserveStoredSecrets: %v", err)
|
|
}
|
|
if merged["api_key"] != "saved" || merged["region"] != "new" {
|
|
t.Fatalf("merged = %#v", merged)
|
|
}
|
|
|
|
replaced, err := service.preserveStoredSecrets(
|
|
context.Background(),
|
|
7,
|
|
"account",
|
|
map[string]any{"api_key": "clawrouter-e2e-secret"},
|
|
[][]string{{"api_key"}},
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("preserveStoredSecrets replacement: %v", err)
|
|
}
|
|
if replaced["api_key"] != "clawrouter-e2e-secret" {
|
|
t.Fatalf("replacement = %#v", replaced)
|
|
}
|
|
}
|
|
|
|
func TestPreserveStoredSecretsMergesNestedObjectsWithoutMutatingInputs(t *testing.T) {
|
|
savedConnection := map[string]any{
|
|
"credentials": map[string]any{
|
|
"api_key": "clawrouter-e2e-secret",
|
|
"labels": []any{"one", "two"},
|
|
},
|
|
"endpoint": "https://old.example.invalid",
|
|
}
|
|
incomingConnection := map[string]any{
|
|
"credentials": map[string]any{
|
|
"account": "updated",
|
|
"api_key": " ",
|
|
},
|
|
"endpoint": "",
|
|
}
|
|
wantSaved := cloneConfigValue(savedConnection)
|
|
wantIncoming := cloneConfigValue(incomingConnection)
|
|
store := &fakeServiceConfigStore{configsByInstallation: map[int][]*RuntimeConfig{
|
|
7: {{
|
|
InstallationID: 7,
|
|
Key: "account",
|
|
Value: map[string]any{"connection": savedConnection},
|
|
}},
|
|
}}
|
|
service := &Service{configs: store}
|
|
|
|
merged, err := service.preserveStoredSecrets(
|
|
context.Background(),
|
|
7,
|
|
"account",
|
|
map[string]any{"connection": incomingConnection},
|
|
[][]string{{"connection", "credentials", "api_key"}},
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("preserveStoredSecrets: %v", err)
|
|
}
|
|
|
|
connection, ok := merged["connection"].(map[string]any)
|
|
if !ok {
|
|
t.Fatalf("merged connection = %#v, want object", merged["connection"])
|
|
}
|
|
credentials, ok := connection["credentials"].(map[string]any)
|
|
if !ok {
|
|
t.Fatalf("merged credentials = %#v, want object", connection["credentials"])
|
|
}
|
|
if credentials["api_key"] != "clawrouter-e2e-secret" || credentials["account"] != "updated" {
|
|
t.Fatalf("merged credentials = %#v", credentials)
|
|
}
|
|
if connection["endpoint"] != "" {
|
|
t.Fatalf("merged endpoint = %#v, want blank non-secret value", connection["endpoint"])
|
|
}
|
|
credentials["api_key"] = "mutated"
|
|
credentials["labels"].([]any)[0] = "mutated"
|
|
connection["endpoint"] = "https://mutated.example.invalid"
|
|
if !reflect.DeepEqual(savedConnection, wantSaved) {
|
|
t.Fatalf("saved input mutated: got %#v want %#v", savedConnection, wantSaved)
|
|
}
|
|
if !reflect.DeepEqual(incomingConnection, wantIncoming) {
|
|
t.Fatalf("incoming input mutated: got %#v want %#v", incomingConnection, wantIncoming)
|
|
}
|
|
|
|
replacement, err := service.preserveStoredSecrets(
|
|
context.Background(),
|
|
7,
|
|
"account",
|
|
map[string]any{"connection": map[string]any{
|
|
"credentials": map[string]any{"api_key": "test-auth-token"},
|
|
}},
|
|
[][]string{{"connection", "credentials", "api_key"}},
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("preserveStoredSecrets replacement: %v", err)
|
|
}
|
|
replacementConnection := replacement["connection"].(map[string]any)
|
|
replacementCredentials := replacementConnection["credentials"].(map[string]any)
|
|
if replacementCredentials["api_key"] != "test-auth-token" {
|
|
t.Fatalf("replacement credentials = %#v", replacementCredentials)
|
|
}
|
|
}
|
|
|
|
func TestSetGlobalConfigWithClearsValidatesTheClearedResult(t *testing.T) {
|
|
for _, tc := range []struct {
|
|
name string
|
|
required bool
|
|
wantError bool
|
|
}{
|
|
{name: "required field is rejected", required: true, wantError: true},
|
|
{name: "optional field is removed", required: false, wantError: false},
|
|
} {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
manifest := connectionTestManifest(t, "silo.metadb", "0.0.36")
|
|
if tc.required {
|
|
manifest.GlobalConfigSchema[0].JsonSchema = `{"type":"object","properties":{"api_key":{"type":"string","format":"password"}},"required":["api_key"],"additionalProperties":false}`
|
|
} else {
|
|
manifest.GlobalConfigSchema[0].JsonSchema = `{"type":"object","properties":{"api_key":{"type":"string","format":"password"}},"additionalProperties":false}`
|
|
}
|
|
installPath := writeInstalledPluginManifest(t, manifest)
|
|
store := &fakeServiceConfigStore{configsByInstallation: map[int][]*RuntimeConfig{
|
|
7: {{
|
|
InstallationID: 7,
|
|
Key: "connection",
|
|
Value: map[string]any{
|
|
"api_key": "clawrouter-e2e-secret",
|
|
"plugin_owned": "retained",
|
|
},
|
|
}},
|
|
}}
|
|
service := &Service{
|
|
installations: newFakeServiceInstallationStore(&Installation{
|
|
ID: 7,
|
|
PluginID: manifest.GetPluginId(),
|
|
Version: manifest.GetVersion(),
|
|
InstallPath: installPath,
|
|
Enabled: true,
|
|
}),
|
|
configs: store,
|
|
}
|
|
|
|
err := service.SetGlobalConfigWithClears(
|
|
context.Background(),
|
|
7,
|
|
"connection",
|
|
map[string]any{},
|
|
[]string{"api_key"},
|
|
)
|
|
if tc.wantError {
|
|
var validationErr *ConfigValidationError
|
|
if !errors.As(err, &validationErr) {
|
|
t.Fatalf("error = %v, want ConfigValidationError", err)
|
|
}
|
|
if len(store.puts) != 0 {
|
|
t.Fatalf("persisted invalid cleared config: %#v", store.puts)
|
|
}
|
|
return
|
|
}
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(store.puts) != 1 {
|
|
t.Fatalf("put calls = %d, want 1", len(store.puts))
|
|
}
|
|
if _, present := store.puts[0].value["api_key"]; present {
|
|
t.Fatalf("cleared field remained in config: %#v", store.puts[0].value)
|
|
}
|
|
if store.puts[0].value["plugin_owned"] != "retained" {
|
|
t.Fatalf("plugin-owned field was not retained: %#v", store.puts[0].value)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestSetGlobalConfigWithClearsPreservesAndExplicitlyClearsNestedSecretObject(t *testing.T) {
|
|
manifest := connectionTestManifest(t, "silo.metadb", "0.0.36")
|
|
manifest.GlobalConfigSchema[0].JsonSchema = `{
|
|
"type":"object",
|
|
"properties":{
|
|
"connection":{
|
|
"type":"object",
|
|
"properties":{
|
|
"api_key":{"type":"string","format":"password"},
|
|
"endpoint":{"type":"string"}
|
|
},
|
|
"additionalProperties":false
|
|
},
|
|
"region":{"type":"string"}
|
|
},
|
|
"additionalProperties":false
|
|
}`
|
|
manifest.GlobalConfigSchema[0].AdminForm = nil
|
|
installPath := writeInstalledPluginManifest(t, manifest)
|
|
store := &fakeServiceConfigStore{configsByInstallation: map[int][]*RuntimeConfig{
|
|
7: {{
|
|
InstallationID: 7,
|
|
Key: "connection",
|
|
Value: map[string]any{
|
|
"connection": map[string]any{
|
|
"api_key": "clawrouter-e2e-secret",
|
|
"endpoint": "https://old.example.invalid",
|
|
},
|
|
"region": "old",
|
|
},
|
|
}},
|
|
}}
|
|
service := &Service{
|
|
installations: newFakeServiceInstallationStore(&Installation{
|
|
ID: 7,
|
|
PluginID: manifest.GetPluginId(),
|
|
Version: manifest.GetVersion(),
|
|
InstallPath: installPath,
|
|
Enabled: true,
|
|
}),
|
|
configs: store,
|
|
}
|
|
|
|
err := service.SetGlobalConfigWithClears(
|
|
context.Background(),
|
|
7,
|
|
"connection",
|
|
map[string]any{
|
|
"connection": map[string]any{
|
|
"api_key": "",
|
|
"endpoint": "",
|
|
},
|
|
"region": "new",
|
|
},
|
|
nil,
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(store.puts) != 1 {
|
|
t.Fatalf("put calls = %d, want 1", len(store.puts))
|
|
}
|
|
connection := store.puts[0].value["connection"].(map[string]any)
|
|
if connection["api_key"] != "clawrouter-e2e-secret" ||
|
|
connection["endpoint"] != "" {
|
|
t.Fatalf("persisted connection = %#v", connection)
|
|
}
|
|
|
|
err = service.SetGlobalConfigWithClears(
|
|
context.Background(),
|
|
7,
|
|
"connection",
|
|
map[string]any{"region": "new"},
|
|
[]string{"connection"},
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(store.puts) != 2 {
|
|
t.Fatalf("put calls = %d, want 2", len(store.puts))
|
|
}
|
|
if _, present := store.puts[1].value["connection"]; present {
|
|
t.Fatalf("explicitly cleared connection remained: %#v", store.puts[1].value)
|
|
}
|
|
}
|
|
|
|
func TestSetGlobalConfigPreservesOmittedPluginOwnedFields(t *testing.T) {
|
|
manifest := connectionTestManifest(t, "silo.metadb", "0.0.36")
|
|
manifest.GlobalConfigSchema[0].JsonSchema = `{
|
|
"type":"object",
|
|
"properties":{
|
|
"display_name":{"type":"string"},
|
|
"settings":{
|
|
"type":"object",
|
|
"properties":{"endpoint":{"type":"string"}},
|
|
"additionalProperties":false
|
|
}
|
|
},
|
|
"additionalProperties":false
|
|
}`
|
|
manifest.GlobalConfigSchema[0].AdminForm = nil
|
|
installPath := writeInstalledPluginManifest(t, manifest)
|
|
pluginState := map[string]any{
|
|
"cursor": "plugin-managed-cursor",
|
|
"options": []any{"one", "two"},
|
|
}
|
|
store := &fakeServiceConfigStore{configsByInstallation: map[int][]*RuntimeConfig{
|
|
7: {{
|
|
InstallationID: 7,
|
|
Key: "connection",
|
|
Value: map[string]any{
|
|
"display_name": "old",
|
|
"plugin_state": pluginState,
|
|
"settings": map[string]any{
|
|
"endpoint": "https://example.invalid",
|
|
"plugin_owned": "retained",
|
|
},
|
|
},
|
|
}},
|
|
}}
|
|
service := &Service{
|
|
installations: newFakeServiceInstallationStore(&Installation{
|
|
ID: 7,
|
|
PluginID: manifest.GetPluginId(),
|
|
Version: manifest.GetVersion(),
|
|
InstallPath: installPath,
|
|
Enabled: true,
|
|
}),
|
|
configs: store,
|
|
}
|
|
|
|
err := service.SetGlobalConfig(
|
|
context.Background(),
|
|
7,
|
|
"connection",
|
|
map[string]any{"display_name": "new"},
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(store.puts) != 1 {
|
|
t.Fatalf("put calls = %d, want 1", len(store.puts))
|
|
}
|
|
if store.puts[0].value["display_name"] != "new" {
|
|
t.Fatalf("display_name = %#v, want new", store.puts[0].value["display_name"])
|
|
}
|
|
if !reflect.DeepEqual(store.puts[0].value["plugin_state"], pluginState) {
|
|
t.Fatalf(
|
|
"plugin_state = %#v, want %#v",
|
|
store.puts[0].value["plugin_state"],
|
|
pluginState,
|
|
)
|
|
}
|
|
wantSettings := map[string]any{
|
|
"endpoint": "https://example.invalid",
|
|
"plugin_owned": "retained",
|
|
}
|
|
if !reflect.DeepEqual(store.puts[0].value["settings"], wantSettings) {
|
|
t.Fatalf(
|
|
"settings = %#v, want %#v",
|
|
store.puts[0].value["settings"],
|
|
wantSettings,
|
|
)
|
|
}
|
|
|
|
err = service.SetGlobalConfig(
|
|
context.Background(),
|
|
7,
|
|
"connection",
|
|
map[string]any{"unexpected": "submitted"},
|
|
)
|
|
var validationErr *ConfigValidationError
|
|
if !errors.As(err, &validationErr) {
|
|
t.Fatalf("submitted opaque field error = %v, want ConfigValidationError", err)
|
|
}
|
|
if len(store.puts) != 1 {
|
|
t.Fatalf("invalid submitted field persisted: %#v", store.puts)
|
|
}
|
|
}
|
|
|
|
func TestSetGlobalConfigRetriesConcurrentMerge(t *testing.T) {
|
|
manifest := connectionTestManifest(t, "silo.metadb", "0.0.36")
|
|
manifest.GlobalConfigSchema[0].JsonSchema = `{
|
|
"type":"object",
|
|
"properties":{"display_name":{"type":"string"}}
|
|
}`
|
|
manifest.GlobalConfigSchema[0].AdminForm = nil
|
|
installPath := writeInstalledPluginManifest(t, manifest)
|
|
store := &fakeServiceConfigStore{
|
|
configsByInstallation: map[int][]*RuntimeConfig{
|
|
7: {{
|
|
InstallationID: 7,
|
|
Key: "connection",
|
|
Value: map[string]any{
|
|
"display_name": "old",
|
|
"plugin_state": map[string]any{"cursor": "old"},
|
|
},
|
|
}},
|
|
},
|
|
casFailures: 1,
|
|
concurrentUpdates: map[string]any{
|
|
"plugin_state": map[string]any{"cursor": "concurrent"},
|
|
},
|
|
}
|
|
service := &Service{
|
|
installations: newFakeServiceInstallationStore(&Installation{
|
|
ID: 7,
|
|
PluginID: manifest.GetPluginId(),
|
|
Version: manifest.GetVersion(),
|
|
InstallPath: installPath,
|
|
Enabled: true,
|
|
}),
|
|
configs: store,
|
|
}
|
|
|
|
err := service.SetGlobalConfig(
|
|
context.Background(),
|
|
7,
|
|
"connection",
|
|
map[string]any{"display_name": "new"},
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if store.casCalls != 2 {
|
|
t.Fatalf("CAS calls = %d, want 2", store.casCalls)
|
|
}
|
|
if len(store.puts) != 1 {
|
|
t.Fatalf("successful writes = %d, want 1", len(store.puts))
|
|
}
|
|
if store.puts[0].value["display_name"] != "new" {
|
|
t.Fatalf("display_name = %#v, want new", store.puts[0].value["display_name"])
|
|
}
|
|
wantState := map[string]any{"cursor": "concurrent"}
|
|
if !reflect.DeepEqual(store.puts[0].value["plugin_state"], wantState) {
|
|
t.Fatalf(
|
|
"plugin_state = %#v, want concurrent update %#v",
|
|
store.puts[0].value["plugin_state"],
|
|
wantState,
|
|
)
|
|
}
|
|
}
|
|
|
|
func (f *fakeServiceConfigStore) ListGlobalConfigs(
|
|
_ context.Context,
|
|
installationID int,
|
|
) ([]*RuntimeConfig, error) {
|
|
configs := f.configsByInstallation[installationID]
|
|
result := make([]*RuntimeConfig, 0, len(configs))
|
|
for _, config := range configs {
|
|
if config == nil {
|
|
continue
|
|
}
|
|
cloned := *config
|
|
cloned.Value = cloneConfigMap(config.Value)
|
|
result = append(result, &cloned)
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func (f *fakeServiceConfigStore) PutGlobalConfig(
|
|
_ context.Context,
|
|
installationID int,
|
|
key string,
|
|
value map[string]any,
|
|
) error {
|
|
f.puts = append(f.puts, putGlobalConfigCall{
|
|
installationID: installationID,
|
|
key: key,
|
|
value: cloneConfigMap(value),
|
|
})
|
|
return f.putErr
|
|
}
|
|
|
|
func (f *fakeServiceConfigStore) CompareAndSwapGlobalConfig(
|
|
_ context.Context,
|
|
installationID int,
|
|
key string,
|
|
value map[string]any,
|
|
expectedUpdatedAt *time.Time,
|
|
) (bool, error) {
|
|
f.casCalls++
|
|
if f.putErr != nil {
|
|
return false, f.putErr
|
|
}
|
|
configs := f.configsByInstallation[installationID]
|
|
var existing *RuntimeConfig
|
|
for _, config := range configs {
|
|
if config != nil && config.Key == key {
|
|
existing = config
|
|
break
|
|
}
|
|
}
|
|
if f.casFailures > 0 {
|
|
f.casFailures--
|
|
if existing != nil {
|
|
for field, update := range f.concurrentUpdates {
|
|
existing.Value[field] = cloneConfigValue(update)
|
|
}
|
|
existing.UpdatedAt = existing.UpdatedAt.Add(time.Second)
|
|
}
|
|
return false, nil
|
|
}
|
|
switch {
|
|
case existing == nil && expectedUpdatedAt != nil:
|
|
return false, nil
|
|
case existing != nil && (expectedUpdatedAt == nil ||
|
|
!existing.UpdatedAt.Equal(*expectedUpdatedAt)):
|
|
return false, nil
|
|
}
|
|
if existing == nil {
|
|
existing = &RuntimeConfig{
|
|
InstallationID: installationID,
|
|
Key: key,
|
|
}
|
|
f.configsByInstallation[installationID] = append(configs, existing)
|
|
}
|
|
existing.Value = cloneConfigValue(value).(map[string]any)
|
|
existing.UpdatedAt = existing.UpdatedAt.Add(time.Second)
|
|
f.puts = append(f.puts, putGlobalConfigCall{
|
|
installationID: installationID,
|
|
key: key,
|
|
value: cloneConfigValue(value).(map[string]any),
|
|
})
|
|
return true, nil
|
|
}
|
|
|
|
func TestServiceTestGlobalConfigUsesMergedDraftAndStopsTemporaryInstance(t *testing.T) {
|
|
originalProbe := runPluginConnectionCheck
|
|
t.Cleanup(func() {
|
|
runPluginConnectionCheck = originalProbe
|
|
})
|
|
|
|
probeCalls := 0
|
|
runPluginConnectionCheck = func(
|
|
_ context.Context,
|
|
client pluginClient,
|
|
manifest *pluginv1.PluginManifest,
|
|
) error {
|
|
probeCalls++
|
|
if client == nil {
|
|
t.Fatal("probe client = nil, want started client")
|
|
}
|
|
if manifest.GetPluginId() != "silo.metadb" {
|
|
t.Fatalf("manifest plugin id = %q, want silo.metadb", manifest.GetPluginId())
|
|
}
|
|
return nil
|
|
}
|
|
|
|
manifest := connectionTestManifest(t, "silo.metadb", "0.0.36")
|
|
manifest.GlobalConfigSchema[0].JsonSchema = `{"type":"object","properties":{"api_key":{"type":"string","format":"password"}},"required":["api_key"],"additionalProperties":false}`
|
|
installPath := writeInstalledPluginManifest(t, manifest)
|
|
host := &fakeServiceHost{
|
|
startResult: &fakePluginClient{manifest: manifest},
|
|
}
|
|
service := &Service{
|
|
installations: newFakeServiceInstallationStore(&Installation{
|
|
ID: 42,
|
|
PluginID: manifest.GetPluginId(),
|
|
Version: manifest.GetVersion(),
|
|
InstallPath: installPath,
|
|
Enabled: false,
|
|
}),
|
|
configs: &fakeServiceConfigStore{
|
|
configsByInstallation: map[int][]*RuntimeConfig{
|
|
42: {
|
|
{
|
|
InstallationID: 42,
|
|
Key: "connection",
|
|
Value: map[string]any{
|
|
"api_key": "persisted",
|
|
},
|
|
},
|
|
{
|
|
InstallationID: 42,
|
|
Key: "secondary",
|
|
Value: map[string]any{
|
|
"enabled": true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
host: host,
|
|
}
|
|
|
|
if err := service.TestGlobalConfig(context.Background(), 42, "connection", map[string]any{
|
|
"api_key": "draft",
|
|
}); err != nil {
|
|
t.Fatalf("TestGlobalConfig() returned error: %v", err)
|
|
}
|
|
|
|
if probeCalls != 1 {
|
|
t.Fatalf("probe calls = %d, want 1", probeCalls)
|
|
}
|
|
if len(host.started) != 1 {
|
|
t.Fatalf("start calls = %d, want 1", len(host.started))
|
|
}
|
|
if len(host.stopped) != 1 {
|
|
t.Fatalf("stop calls = %d, want 1", len(host.stopped))
|
|
}
|
|
|
|
startReq := host.started[0]
|
|
if startReq.InstallationID >= 0 {
|
|
t.Fatalf("temporary installation id = %d, want negative id", startReq.InstallationID)
|
|
}
|
|
if host.stopped[0] != startReq.InstallationID {
|
|
t.Fatalf("stopped installation id = %d, want %d", host.stopped[0], startReq.InstallationID)
|
|
}
|
|
if len(startReq.Config) != 2 {
|
|
t.Fatalf("config entries = %d, want 2", len(startReq.Config))
|
|
}
|
|
|
|
valuesByKey := make(map[string]map[string]any, len(startReq.Config))
|
|
for _, entry := range startReq.Config {
|
|
valuesByKey[entry.GetKey()] = entry.GetValue().AsMap()
|
|
}
|
|
if got := valuesByKey["connection"]["api_key"]; got != "draft" {
|
|
t.Fatalf("connection api_key = %#v, want draft", got)
|
|
}
|
|
if got := valuesByKey["secondary"]["enabled"]; got != true {
|
|
t.Fatalf("secondary enabled = %#v, want true", got)
|
|
}
|
|
|
|
err := service.TestGlobalConfigWithClears(
|
|
context.Background(),
|
|
42,
|
|
"connection",
|
|
map[string]any{},
|
|
[]string{"api_key"},
|
|
)
|
|
var connectionErr *ConnectionTestError
|
|
if !errors.As(err, &connectionErr) {
|
|
t.Fatalf("cleared required secret error = %v, want ConnectionTestError", err)
|
|
}
|
|
if probeCalls != 1 || len(host.started) != 1 {
|
|
t.Fatalf("invalid cleared config reached probe: probes=%d starts=%d", probeCalls, len(host.started))
|
|
}
|
|
}
|
|
|
|
func TestRunPluginConnectionCheckSkipsMovieProbeForAudiobookOnlyProvider(t *testing.T) {
|
|
metadata, err := structpb.NewStruct(map[string]any{
|
|
"default_priority": map[string]any{
|
|
"audiobook": 2,
|
|
},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("NewStruct() error = %v", err)
|
|
}
|
|
|
|
manifest := connectionTestManifest(t, "silo.audiobook-metadata", "0.1.2")
|
|
manifest.Capabilities = []*pluginv1.CapabilityDescriptor{
|
|
{
|
|
Type: "metadata_provider.v1",
|
|
Id: "audiobook-metadata",
|
|
DisplayName: "Audiobook Metadata",
|
|
Metadata: metadata,
|
|
},
|
|
}
|
|
client := &fakePluginClient{manifest: manifest}
|
|
|
|
if err := runPluginConnectionCheck(context.Background(), client, manifest); err != nil {
|
|
t.Fatalf("runPluginConnectionCheck() error = %v", err)
|
|
}
|
|
if client.metadataProviderCalls != 0 {
|
|
t.Fatalf("metadata provider calls = %d, want 0", client.metadataProviderCalls)
|
|
}
|
|
}
|
|
|
|
func TestServiceTestGlobalConfigReturnsUnsupportedWithoutStartingPlugin(t *testing.T) {
|
|
manifest := connectionTestManifest(t, "silo.simple", "0.0.1")
|
|
manifest.Capabilities = []*pluginv1.CapabilityDescriptor{
|
|
{
|
|
Type: "scheduled_task.v1",
|
|
Id: "refresh",
|
|
DisplayName: "Refresh",
|
|
},
|
|
}
|
|
installPath := writeInstalledPluginManifest(t, manifest)
|
|
host := &fakeServiceHost{}
|
|
service := &Service{
|
|
installations: newFakeServiceInstallationStore(&Installation{
|
|
ID: 7,
|
|
PluginID: manifest.GetPluginId(),
|
|
Version: manifest.GetVersion(),
|
|
InstallPath: installPath,
|
|
Enabled: true,
|
|
}),
|
|
host: host,
|
|
}
|
|
|
|
err := service.TestGlobalConfig(context.Background(), 7, "connection", map[string]any{
|
|
"api_key": "draft",
|
|
})
|
|
if err == nil {
|
|
t.Fatal("TestGlobalConfig() returned nil error, want unsupported error")
|
|
}
|
|
|
|
var connectionErr *ConnectionTestError
|
|
if !errors.As(err, &connectionErr) {
|
|
t.Fatalf("error = %v, want ConnectionTestError", err)
|
|
}
|
|
if !strings.Contains(connectionErr.Error(), "not supported") {
|
|
t.Fatalf("connection error message = %q, want unsupported message", connectionErr.Error())
|
|
}
|
|
if len(host.started) != 0 {
|
|
t.Fatalf("start calls = %d, want 0", len(host.started))
|
|
}
|
|
if len(host.stopped) != 0 {
|
|
t.Fatalf("stop calls = %d, want 0", len(host.stopped))
|
|
}
|
|
}
|
|
|
|
func TestServiceTestGlobalConfigStopsTemporaryInstanceOnProbeFailure(t *testing.T) {
|
|
originalProbe := runPluginConnectionCheck
|
|
t.Cleanup(func() {
|
|
runPluginConnectionCheck = originalProbe
|
|
})
|
|
|
|
runPluginConnectionCheck = func(
|
|
_ context.Context,
|
|
_ pluginClient,
|
|
_ *pluginv1.PluginManifest,
|
|
) error {
|
|
return &ConnectionTestError{Message: "probe failed"}
|
|
}
|
|
|
|
manifest := connectionTestManifest(t, "silo.metadb", "0.0.36")
|
|
installPath := writeInstalledPluginManifest(t, manifest)
|
|
host := &fakeServiceHost{
|
|
startResult: &fakePluginClient{manifest: manifest},
|
|
}
|
|
service := &Service{
|
|
installations: newFakeServiceInstallationStore(&Installation{
|
|
ID: 19,
|
|
PluginID: manifest.GetPluginId(),
|
|
Version: manifest.GetVersion(),
|
|
InstallPath: installPath,
|
|
Enabled: true,
|
|
}),
|
|
host: host,
|
|
}
|
|
|
|
err := service.TestGlobalConfig(context.Background(), 19, "connection", map[string]any{
|
|
"api_key": "draft",
|
|
})
|
|
if err == nil {
|
|
t.Fatal("TestGlobalConfig() returned nil error, want probe failure")
|
|
}
|
|
if len(host.started) != 1 {
|
|
t.Fatalf("start calls = %d, want 1", len(host.started))
|
|
}
|
|
if len(host.stopped) != 1 {
|
|
t.Fatalf("stop calls = %d, want 1", len(host.stopped))
|
|
}
|
|
if host.stopped[0] != host.started[0].InstallationID {
|
|
t.Fatalf("stopped installation id = %d, want %d", host.stopped[0], host.started[0].InstallationID)
|
|
}
|
|
}
|
|
|
|
func TestServiceTestGlobalConfigUsesUniqueTemporaryInstallationIDs(t *testing.T) {
|
|
originalProbe := runPluginConnectionCheck
|
|
t.Cleanup(func() {
|
|
runPluginConnectionCheck = originalProbe
|
|
})
|
|
|
|
runPluginConnectionCheck = func(
|
|
_ context.Context,
|
|
_ pluginClient,
|
|
_ *pluginv1.PluginManifest,
|
|
) error {
|
|
return nil
|
|
}
|
|
|
|
manifest := connectionTestManifest(t, "silo.metadb", "0.0.36")
|
|
installPath := writeInstalledPluginManifest(t, manifest)
|
|
host := &fakeServiceHost{
|
|
startResult: &fakePluginClient{manifest: manifest},
|
|
}
|
|
service := &Service{
|
|
installations: newFakeServiceInstallationStore(&Installation{
|
|
ID: 5,
|
|
PluginID: manifest.GetPluginId(),
|
|
Version: manifest.GetVersion(),
|
|
InstallPath: installPath,
|
|
Enabled: true,
|
|
}),
|
|
host: host,
|
|
}
|
|
|
|
if err := service.TestGlobalConfig(context.Background(), 5, "connection", map[string]any{
|
|
"api_key": "first",
|
|
}); err != nil {
|
|
t.Fatalf("first TestGlobalConfig() returned error: %v", err)
|
|
}
|
|
if err := service.TestGlobalConfig(context.Background(), 5, "connection", map[string]any{
|
|
"api_key": "second",
|
|
}); err != nil {
|
|
t.Fatalf("second TestGlobalConfig() returned error: %v", err)
|
|
}
|
|
|
|
if len(host.started) != 2 {
|
|
t.Fatalf("start calls = %d, want 2", len(host.started))
|
|
}
|
|
if host.started[0].InstallationID == host.started[1].InstallationID {
|
|
t.Fatalf("temporary installation ids matched: %d", host.started[0].InstallationID)
|
|
}
|
|
}
|
|
|
|
func connectionTestManifest(t *testing.T, pluginID, version string) *pluginv1.PluginManifest {
|
|
t.Helper()
|
|
|
|
manifest := testPluginManifest(t, pluginID, version)
|
|
manifest.GlobalConfigSchema = []*pluginv1.ConfigSchema{
|
|
{
|
|
Key: "connection",
|
|
Title: "Connection",
|
|
Required: true,
|
|
JsonSchema: `{"type":"object","properties":{"api_key":{"type":"string"}},"required":["api_key"],"additionalProperties":false}`,
|
|
},
|
|
}
|
|
return manifest
|
|
}
|