Files
probo/pkg/connector/provider/registry_test.go
Sacha Al Himdani 4c57d201a4 Make license declarations consistently MIT
The source headers, LICENSE files, and license metadata had drifted
apart. Align the entire project to MIT:

- Convert every source-file header to the MIT text across all comment
  styles (Go, TS, TSX, JS, MJS, SQL, CSS, GraphQL, shell), including
  SPDX-License-Identifier tags
- Set the root and cookie-banner LICENSE files to the MIT text with a
  "MIT License" title line
- Switch the package.json license fields, Docker image label, and
  cookie-banner README to MIT
- Update docs and the genmodels header generator accordingly
- Normalize copyright lines to a single format
  (Copyright (c) <year(s)> Probo Inc <hello@probo.com>.): unify the
  hello@getprobo.com and hello@probo.inc emails to hello@probo.com and
  the comma-separated years to a hyphenated range

Genuine third-party references are intentionally left untouched: the
Lucide icon attributions (Lucide is ISC) and the trivy dependency
license allowlist.

Signed-off-by: Sacha Al Himdani <sacha@probo.com>
2026-07-13 16:21:14 +02:00

376 lines
13 KiB
Go

// Copyright (c) 2026 Probo Inc <hello@probo.com>.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
package provider_test
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.probo.inc/probo/pkg/connector"
"go.probo.inc/probo/pkg/connector/provider"
"go.probo.inc/probo/pkg/coredata"
)
// TestEveryProviderRegistered asserts that every
// coredata.ConnectorProvider constant has a matching Registration in
// the registry, that the registration carries the minimum metadata
// (Provider, DisplayName), and that the access-review NewDriver
// closure is wired — so the provider can actually drive a review.
func TestEveryProviderRegistered(t *testing.T) {
t.Parallel()
r := provider.NewBuiltinRegistry()
for _, p := range coredata.ConnectorProviders() {
t.Run(string(p), func(t *testing.T) {
t.Parallel()
reg, ok := r.Get(p)
require.Truef(t, ok, "provider %q has no Registration", p)
require.NotNil(t, reg, "provider %q Registration is nil", p)
require.Equalf(t, p, reg.Provider, "provider %q has mismatching Registration.Provider", p)
assert.NotEmptyf(t, reg.DisplayName, "provider %q has empty DisplayName", p)
assert.NotNilf(t, reg.NewDriver, "provider %q has nil NewDriver", p)
})
}
}
// TestRegistry_Register exercises the validation and duplicate-detection
// paths on Register. Programmer errors at NewBuiltinRegistry time —
// nil, empty Provider, empty DisplayName, duplicate — must all surface
// as errors rather than silently registering a malformed entry.
func TestRegistry_Register(t *testing.T) {
t.Parallel()
t.Run("nil Registration", func(t *testing.T) {
t.Parallel()
r := provider.NewRegistry()
err := r.Register(nil)
require.Error(t, err)
assert.Contains(t, err.Error(), "nil Registration")
})
t.Run("empty Provider", func(t *testing.T) {
t.Parallel()
r := provider.NewRegistry()
err := r.Register(&provider.Registration{DisplayName: "X"})
require.Error(t, err)
assert.Contains(t, err.Error(), "missing Provider")
})
t.Run("empty DisplayName", func(t *testing.T) {
t.Parallel()
r := provider.NewRegistry()
err := r.Register(&provider.Registration{Provider: coredata.ConnectorProviderSlack})
require.Error(t, err)
assert.Contains(t, err.Error(), "missing DisplayName")
})
t.Run("APIKeyBasicAuth and APIKeyHeader mutually exclusive", func(t *testing.T) {
t.Parallel()
r := provider.NewRegistry()
err := r.Register(&provider.Registration{
Provider: coredata.ConnectorProviderSlack,
DisplayName: "Slack",
APIKeyBasicAuth: true,
APIKeyHeader: "x-api-key",
})
require.Error(t, err)
assert.Contains(t, err.Error(), "mutually exclusive")
})
t.Run("APIKeyAuthScheme and APIKeyHeader mutually exclusive", func(t *testing.T) {
t.Parallel()
r := provider.NewRegistry()
err := r.Register(&provider.Registration{
Provider: coredata.ConnectorProviderSlack,
DisplayName: "Slack",
APIKeyAuthScheme: "SSWS",
APIKeyHeader: "x-api-key",
})
require.Error(t, err)
assert.Contains(t, err.Error(), "mutually exclusive")
})
t.Run("APIKeyBasicAuthUserPass and APIKeyHeader mutually exclusive", func(t *testing.T) {
t.Parallel()
r := provider.NewRegistry()
err := r.Register(&provider.Registration{
Provider: coredata.ConnectorProviderSlack,
DisplayName: "Slack",
APIKeyBasicAuthUserPass: true,
APIKeyHeader: "x-api-key",
})
require.Error(t, err)
assert.Contains(t, err.Error(), "mutually exclusive")
})
t.Run("BuildTokenURLForDomain and BuildTokenURLForSite mutually exclusive", func(t *testing.T) {
t.Parallel()
r := provider.NewRegistry()
err := r.Register(&provider.Registration{
Provider: coredata.ConnectorProviderSlack,
DisplayName: "Slack",
BuildTokenURLForDomain: func(string) (string, error) { return "", nil },
BuildTokenURLForSite: func(string) (string, error) { return "", nil },
})
require.Error(t, err)
assert.Contains(t, err.Error(), "mutually exclusive")
})
t.Run("RequiresManagedResourceID requires ManagedAPIKey", func(t *testing.T) {
t.Parallel()
r := provider.NewRegistry()
err := r.Register(&provider.Registration{
Provider: coredata.ConnectorProviderSlack,
DisplayName: "Slack",
RequiresManagedResourceID: true,
})
require.Error(t, err)
assert.Contains(t, err.Error(), "RequiresManagedResourceID requires ManagedAPIKey")
})
t.Run("duplicate registration", func(t *testing.T) {
t.Parallel()
r := provider.NewRegistry()
require.NoError(t, r.Register(&provider.Registration{
Provider: coredata.ConnectorProviderSlack,
DisplayName: "Slack",
}))
err := r.Register(&provider.Registration{
Provider: coredata.ConnectorProviderSlack,
DisplayName: "Slack-bis",
})
require.Error(t, err)
assert.Contains(t, err.Error(), "duplicate registration")
})
t.Run("valid Registration round-trips through Get", func(t *testing.T) {
t.Parallel()
r := provider.NewRegistry()
want := &provider.Registration{
Provider: coredata.ConnectorProviderSlack,
DisplayName: "Slack",
}
require.NoError(t, r.Register(want))
got, ok := r.Get(coredata.ConnectorProviderSlack)
require.True(t, ok)
assert.Same(t, want, got)
})
}
// TestRegistry_All asserts the registry returns the same number of
// entries that have been registered. The builtin registry is the
// canonical source of truth: every coredata.ConnectorProvider has
// exactly one matching Registration, no more.
func TestRegistry_All(t *testing.T) {
t.Parallel()
r := provider.NewBuiltinRegistry()
assert.Len(t, r.All(), len(coredata.ConnectorProviders()))
}
// TestRegistry_ProviderDisplayName covers the fallback path: an
// unregistered provider returns its raw constant string.
func TestRegistry_ProviderDisplayName(t *testing.T) {
t.Parallel()
r := provider.NewBuiltinRegistry()
assert.Equal(t, "Slack", r.ProviderDisplayName(coredata.ConnectorProviderSlack))
assert.Equal(t, "UNKNOWN", r.ProviderDisplayName(coredata.ConnectorProvider("UNKNOWN")))
}
// TestRegistry_ProviderOAuth2Scopes covers the nil path for an
// unregistered provider and the populated path for a registered one.
func TestRegistry_ProviderOAuth2Scopes(t *testing.T) {
t.Parallel()
r := provider.NewBuiltinRegistry()
assert.NotEmpty(t, r.ProviderOAuth2Scopes(coredata.ConnectorProviderSlack))
assert.Nil(t, r.ProviderOAuth2Scopes(coredata.ConnectorProvider("UNKNOWN")))
}
// TestRegistry_ProbeURL covers the registered and unregistered paths.
// Slack ships a probe URL in its Registration; an unknown provider
// returns the empty string.
func TestRegistry_ProbeURL(t *testing.T) {
t.Parallel()
r := provider.NewBuiltinRegistry()
assert.NotEmpty(t, r.ProbeURL("SLACK"))
assert.Empty(t, r.ProbeURL("UNKNOWN"))
}
// TestRegistry_ManagedAPIKey covers the deactivated default (no key
// configured), a configured key, and that an empty key is a no-op so
// the provider stays deactivated.
func TestRegistry_ManagedAPIKey(t *testing.T) {
t.Parallel()
r := provider.NewRegistry()
key, ok := r.ManagedAPIKey(coredata.ConnectorProviderCrisp)
assert.False(t, ok)
assert.Empty(t, key)
r.SetManagedAPIKey(coredata.ConnectorProviderCrisp, "")
_, ok = r.ManagedAPIKey(coredata.ConnectorProviderCrisp)
assert.False(t, ok, "empty key must not configure the provider")
r.SetManagedAPIKey(coredata.ConnectorProviderCrisp, "identifier:secret")
key, ok = r.ManagedAPIKey(coredata.ConnectorProviderCrisp)
assert.True(t, ok)
assert.Equal(t, "identifier:secret", key)
}
func TestRegistry_ManagedResourceID(t *testing.T) {
t.Parallel()
r := provider.NewRegistry()
id, ok := r.ManagedResourceID(coredata.ConnectorProviderCrisp)
assert.False(t, ok)
assert.Empty(t, id)
r.SetManagedResourceID(coredata.ConnectorProviderCrisp, "")
_, ok = r.ManagedResourceID(coredata.ConnectorProviderCrisp)
assert.False(t, ok, "empty resource id must not configure the provider")
r.SetManagedResourceID(coredata.ConnectorProviderCrisp, "plugin-id")
id, ok = r.ManagedResourceID(coredata.ConnectorProviderCrisp)
assert.True(t, ok)
assert.Equal(t, "plugin-id", id)
}
// TestCrispIsManagedAPIKey pins Crisp's Model B shape: it is a managed
// API-key provider that does not accept a customer-pasted key, so the
// driver catalog hides it until the operator configures the plugin
// token.
func TestCrispIsManagedAPIKey(t *testing.T) {
t.Parallel()
r := provider.NewBuiltinRegistry()
reg, ok := r.Get(coredata.ConnectorProviderCrisp)
require.True(t, ok)
assert.True(t, reg.ManagedAPIKey)
assert.False(t, reg.SupportsAPIKey)
assert.True(t, reg.APIKeyBasicAuthUserPass)
assert.True(t, reg.RequiresManagedResourceID, "crisp needs the plugin ID before it can connect")
}
// TestRegistry_ManagedConnectorReady pins that a provider requiring a resource
// ID (Crisp's plugin ID) is reported ready, and thus surfaced in the catalog,
// only once BOTH the managed key and the resource ID are configured.
func TestRegistry_ManagedConnectorReady(t *testing.T) {
t.Parallel()
t.Run("crisp needs both key and resource id", func(t *testing.T) {
t.Parallel()
r := provider.NewBuiltinRegistry()
assert.False(t, r.ManagedConnectorReady(coredata.ConnectorProviderCrisp), "unconfigured")
r.SetManagedAPIKey(coredata.ConnectorProviderCrisp, "identifier:secret")
assert.False(t, r.ManagedConnectorReady(coredata.ConnectorProviderCrisp), "key set but plugin id missing")
r.SetManagedResourceID(coredata.ConnectorProviderCrisp, "plugin-id")
assert.True(t, r.ManagedConnectorReady(coredata.ConnectorProviderCrisp), "key and plugin id set")
})
t.Run("non-managed provider is never ready", func(t *testing.T) {
t.Parallel()
r := provider.NewBuiltinRegistry()
r.SetManagedAPIKey(coredata.ConnectorProviderTally, "some-key")
assert.False(t, r.ManagedConnectorReady(coredata.ConnectorProviderTally))
})
}
// TestRegistry_RejectsManagedPlusCustomerCredential pins that a
// ManagedAPIKey registration cannot also advertise a customer-supplied
// credential path, whose value would be silently discarded.
func TestRegistry_RejectsManagedPlusCustomerCredential(t *testing.T) {
t.Parallel()
r := provider.NewRegistry()
err := r.Register(&provider.Registration{
Provider: coredata.ConnectorProviderCrisp,
DisplayName: "Crisp",
ManagedAPIKey: true,
SupportsAPIKey: true,
})
require.Error(t, err)
assert.Contains(t, err.Error(), "mutually exclusive")
}
// TestRegistry_ApplyManagedAPIKey verifies the key is injected fresh into a
// managed provider's connection (so rotation propagates and the key is not
// persisted), while non-managed providers are left untouched.
func TestRegistry_ApplyManagedAPIKey(t *testing.T) {
t.Parallel()
r := provider.NewBuiltinRegistry()
r.SetManagedAPIKey(coredata.ConnectorProviderCrisp, "identifier:secret")
managed := &coredata.Connector{
Provider: coredata.ConnectorProviderCrisp,
Connection: &connector.APIKeyConnection{BasicAuthUserPass: true},
}
require.NoError(t, r.ApplyManagedAPIKey(managed))
assert.Equal(t, "identifier:secret", managed.Connection.(*connector.APIKeyConnection).APIKey)
// Non-managed provider: the connection is left untouched.
other := &coredata.Connector{
Provider: coredata.ConnectorProviderSlack,
Connection: &connector.APIKeyConnection{APIKey: "customer-key"},
}
require.NoError(t, r.ApplyManagedAPIKey(other))
assert.Equal(t, "customer-key", other.Connection.(*connector.APIKeyConnection).APIKey)
}
// TestRegistry_ApplyManagedAPIKey_Unconfigured verifies that a managed
// provider whose key was never configured (deactivated) errors rather than
// silently building a keyless client.
func TestRegistry_ApplyManagedAPIKey_Unconfigured(t *testing.T) {
t.Parallel()
r := provider.NewBuiltinRegistry()
managed := &coredata.Connector{
Provider: coredata.ConnectorProviderCrisp,
Connection: &connector.APIKeyConnection{BasicAuthUserPass: true},
}
err := r.ApplyManagedAPIKey(managed)
require.Error(t, err)
assert.Contains(t, err.Error(), "not configured")
}