Files
netbird/management/server/instance/manager_test.go
Misha Bragin e586c20e36 [management, infrastructure, idp] Simplified IdP Management - Embedded IdP (#5008)
Embed Dex as a built-in IdP to simplify self-hosting setup.
Adds an embedded OIDC Identity Provider (Dex) with local user management and optional external IdP connectors (Google/GitHub/OIDC/SAML), plus device-auth flow for CLI login. Introduces instance onboarding/setup endpoints (including owner creation), field-level encryption for sensitive user data, a streamlined self-hosting provisioning script, and expanded APIs + test coverage for IdP management.

more at https://github.com/netbirdio/netbird/pull/5008#issuecomment-3718987393
2026-01-07 14:52:32 +01:00

269 lines
7.4 KiB
Go

package instance
import (
"context"
"errors"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/netbirdio/netbird/management/server/idp"
)
// mockStore implements a minimal store.Store for testing
type mockStore struct {
accountsCount int64
err error
}
func (m *mockStore) GetAccountsCounter(ctx context.Context) (int64, error) {
if m.err != nil {
return 0, m.err
}
return m.accountsCount, nil
}
// mockEmbeddedIdPManager wraps the real EmbeddedIdPManager for testing
type mockEmbeddedIdPManager struct {
createUserFunc func(ctx context.Context, email, password, name string) (*idp.UserData, error)
}
func (m *mockEmbeddedIdPManager) CreateUserWithPassword(ctx context.Context, email, password, name string) (*idp.UserData, error) {
if m.createUserFunc != nil {
return m.createUserFunc(ctx, email, password, name)
}
return &idp.UserData{
ID: "test-user-id",
Email: email,
Name: name,
}, nil
}
// testManager is a test implementation that accepts our mock types
type testManager struct {
store *mockStore
embeddedIdpManager *mockEmbeddedIdPManager
}
func (m *testManager) IsSetupRequired(ctx context.Context) (bool, error) {
if m.embeddedIdpManager == nil {
return false, nil
}
count, err := m.store.GetAccountsCounter(ctx)
if err != nil {
return false, err
}
return count == 0, nil
}
func (m *testManager) CreateOwnerUser(ctx context.Context, email, password, name string) (*idp.UserData, error) {
if m.embeddedIdpManager == nil {
return nil, errors.New("embedded IDP is not enabled")
}
return m.embeddedIdpManager.CreateUserWithPassword(ctx, email, password, name)
}
func TestIsSetupRequired_EmbeddedIdPDisabled(t *testing.T) {
manager := &testManager{
store: &mockStore{accountsCount: 0},
embeddedIdpManager: nil, // No embedded IDP
}
required, err := manager.IsSetupRequired(context.Background())
require.NoError(t, err)
assert.False(t, required, "setup should not be required when embedded IDP is disabled")
}
func TestIsSetupRequired_NoAccounts(t *testing.T) {
manager := &testManager{
store: &mockStore{accountsCount: 0},
embeddedIdpManager: &mockEmbeddedIdPManager{},
}
required, err := manager.IsSetupRequired(context.Background())
require.NoError(t, err)
assert.True(t, required, "setup should be required when no accounts exist")
}
func TestIsSetupRequired_AccountsExist(t *testing.T) {
manager := &testManager{
store: &mockStore{accountsCount: 1},
embeddedIdpManager: &mockEmbeddedIdPManager{},
}
required, err := manager.IsSetupRequired(context.Background())
require.NoError(t, err)
assert.False(t, required, "setup should not be required when accounts exist")
}
func TestIsSetupRequired_MultipleAccounts(t *testing.T) {
manager := &testManager{
store: &mockStore{accountsCount: 5},
embeddedIdpManager: &mockEmbeddedIdPManager{},
}
required, err := manager.IsSetupRequired(context.Background())
require.NoError(t, err)
assert.False(t, required, "setup should not be required when multiple accounts exist")
}
func TestIsSetupRequired_StoreError(t *testing.T) {
manager := &testManager{
store: &mockStore{err: errors.New("database error")},
embeddedIdpManager: &mockEmbeddedIdPManager{},
}
_, err := manager.IsSetupRequired(context.Background())
assert.Error(t, err, "should return error when store fails")
}
func TestCreateOwnerUser_Success(t *testing.T) {
expectedEmail := "admin@example.com"
expectedName := "Admin User"
expectedPassword := "securepassword123"
manager := &testManager{
store: &mockStore{accountsCount: 0},
embeddedIdpManager: &mockEmbeddedIdPManager{
createUserFunc: func(ctx context.Context, email, password, name string) (*idp.UserData, error) {
assert.Equal(t, expectedEmail, email)
assert.Equal(t, expectedPassword, password)
assert.Equal(t, expectedName, name)
return &idp.UserData{
ID: "created-user-id",
Email: email,
Name: name,
}, nil
},
},
}
userData, err := manager.CreateOwnerUser(context.Background(), expectedEmail, expectedPassword, expectedName)
require.NoError(t, err)
assert.Equal(t, "created-user-id", userData.ID)
assert.Equal(t, expectedEmail, userData.Email)
assert.Equal(t, expectedName, userData.Name)
}
func TestCreateOwnerUser_EmbeddedIdPDisabled(t *testing.T) {
manager := &testManager{
store: &mockStore{accountsCount: 0},
embeddedIdpManager: nil,
}
_, err := manager.CreateOwnerUser(context.Background(), "admin@example.com", "password123", "Admin")
assert.Error(t, err, "should return error when embedded IDP is disabled")
assert.Contains(t, err.Error(), "embedded IDP is not enabled")
}
func TestCreateOwnerUser_IdPError(t *testing.T) {
manager := &testManager{
store: &mockStore{accountsCount: 0},
embeddedIdpManager: &mockEmbeddedIdPManager{
createUserFunc: func(ctx context.Context, email, password, name string) (*idp.UserData, error) {
return nil, errors.New("user already exists")
},
},
}
_, err := manager.CreateOwnerUser(context.Background(), "admin@example.com", "password123", "Admin")
assert.Error(t, err, "should return error when IDP fails")
}
func TestDefaultManager_ValidateSetupRequest(t *testing.T) {
manager := &DefaultManager{
setupRequired: true,
}
tests := []struct {
name string
email string
password string
userName string
expectError bool
errorMsg string
}{
{
name: "valid request",
email: "admin@example.com",
password: "password123",
userName: "Admin User",
expectError: false,
},
{
name: "empty email",
email: "",
password: "password123",
userName: "Admin User",
expectError: true,
errorMsg: "email is required",
},
{
name: "invalid email format",
email: "not-an-email",
password: "password123",
userName: "Admin User",
expectError: true,
errorMsg: "invalid email format",
},
{
name: "empty name",
email: "admin@example.com",
password: "password123",
userName: "",
expectError: true,
errorMsg: "name is required",
},
{
name: "empty password",
email: "admin@example.com",
password: "",
userName: "Admin User",
expectError: true,
errorMsg: "password is required",
},
{
name: "password too short",
email: "admin@example.com",
password: "short",
userName: "Admin User",
expectError: true,
errorMsg: "password must be at least 8 characters",
},
{
name: "password exactly 8 characters",
email: "admin@example.com",
password: "12345678",
userName: "Admin User",
expectError: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := manager.validateSetupInfo(tt.email, tt.password, tt.userName)
if tt.expectError {
require.Error(t, err)
assert.Contains(t, err.Error(), tt.errorMsg)
} else {
require.NoError(t, err)
}
})
}
}
func TestDefaultManager_CreateOwnerUser_SetupAlreadyCompleted(t *testing.T) {
manager := &DefaultManager{
setupRequired: false,
embeddedIdpManager: &idp.EmbeddedIdPManager{},
}
_, err := manager.CreateOwnerUser(context.Background(), "admin@example.com", "password123", "Admin")
require.Error(t, err)
assert.Contains(t, err.Error(), "setup already completed")
}