     1→package metrics
     2→
     3→import (
     4→	"context"
     5→	"testing"
     6→
     7→	nbdns "github.com/netbirdio/netbird/dns"
     8→	"github.com/netbirdio/netbird/idp/dex"
     9→	resourceTypes "github.com/netbirdio/netbird/management/server/networks/resources/types"
    10→	routerTypes "github.com/netbirdio/netbird/management/server/networks/routers/types"
    11→	networkTypes "github.com/netbirdio/netbird/management/server/networks/types"
    12→	nbpeer "github.com/netbirdio/netbird/management/server/peer"
    13→	"github.com/netbirdio/netbird/management/server/posture"
    14→	"github.com/netbirdio/netbird/management/server/types"
    15→	"github.com/netbirdio/netbird/route"
    16→)
    17→
    18→type mockDatasource struct{}
    19→
    20→// GetAllConnectedPeers returns a map of connected peer IDs for use in tests with predefined information
    21→func (mockDatasource) GetAllConnectedPeers() map[string]struct{} {
    22→	return map[string]struct{}{
    23→		"1": {},
    24→	}
    25→}
    26→
    27→// GetAllAccounts returns a list of *server.Account for use in tests with predefined information
    28→func (mockDatasource) GetAllAccounts(_ context.Context) []*types.Account {
    29→	localUserID := dex.EncodeDexUserID("10", "local")
    30→	idpUserID := dex.EncodeDexUserID("20", "zitadel")
    31→	return []*types.Account{
    32→		{
    33→			Id:       "1",
    34→			Settings: &types.Settings{PeerLoginExpirationEnabled: true},
    35→			SetupKeys: map[string]*types.SetupKey{
    36→				"1": {
    37→					Id:        "1",
    38→					Ephemeral: true,
    39→					UsedTimes: 1,
    40→				},
    41→			},
    42→			Groups: map[string]*types.Group{
    43→				"1": {},
    44→				"2": {},
    45→			},
    46→			NameServerGroups: map[string]*nbdns.NameServerGroup{
    47→				"1": {},
    48→			},
    49→			Peers: map[string]*nbpeer.Peer{
    50→				"1": {
    51→					ID:         "1",
    52→					UserID:     "test",
    53→					SSHEnabled: false,
    54→					Meta:       nbpeer.PeerSystemMeta{GoOS: "linux", WtVersion: "0.0.1", Flags: nbpeer.Flags{ServerSSHAllowed: true, RosenpassEnabled: true}},
    55→				},
    56→			},
    57→			Policies: []*types.Policy{
    58→				{
    59→					Rules: []*types.PolicyRule{
    60→						{
    61→							Bidirectional: true,
    62→							Protocol:      types.PolicyRuleProtocolTCP,
    63→						},
    64→					},
    65→				},
    66→				{
    67→					Rules: []*types.PolicyRule{
    68→						{
    69→							Bidirectional: false,
    70→							Protocol:      types.PolicyRuleProtocolTCP,
    71→						},
    72→					},
    73→					SourcePostureChecks: []string{"1"},
    74→				},
    75→			},
    76→			Routes: map[route.ID]*route.Route{
    77→				"1": {
    78→					ID:         "1",
    79→					PeerGroups: make([]string, 1),
    80→				},
    81→			},
    82→			PostureChecks: []*posture.Checks{
    83→				{
    84→					ID:   "1",
    85→					Name: "test",
    86→					Checks: posture.ChecksDefinition{
    87→						NBVersionCheck: &posture.NBVersionCheck{
    88→							MinVersion: "0.0.1",
    89→						},
    90→					},
    91→				},
    92→				{
    93→					ID:   "2",
    94→					Name: "tes2",
    95→					Checks: posture.ChecksDefinition{
    96→						NBVersionCheck: &posture.NBVersionCheck{
    97→							MinVersion: "0.0.2",
    98→						},
    99→					},
   100→				},
   101→			},
   102→			Users: map[string]*types.User{
   103→				"1": {
   104→					Id:            "1",
   105→					IsServiceUser: true,
   106→					PATs: map[string]*types.PersonalAccessToken{
   107→						"1": {},
   108→					},
   109→				},
   110→				localUserID: {
   111→					Id:            localUserID,
   112→					IsServiceUser: false,
   113→					PATs: map[string]*types.PersonalAccessToken{
   114→						"1": {},
   115→					},
   116→				},
   117→			},
   118→		},
   119→		{
   120→			Id:       "2",
   121→			Settings: &types.Settings{PeerLoginExpirationEnabled: true},
   122→			SetupKeys: map[string]*types.SetupKey{
   123→				"1": {
   124→					Id:        "1",
   125→					Ephemeral: true,
   126→					UsedTimes: 1,
   127→				},
   128→			},
   129→			Groups: map[string]*types.Group{
   130→				"1": {},
   131→				"2": {},
   132→			},
   133→			NameServerGroups: map[string]*nbdns.NameServerGroup{
   134→				"1": {},
   135→			},
   136→			Peers: map[string]*nbpeer.Peer{
   137→				"1": {
   138→					ID:         "1",
   139→					UserID:     "test",
   140→					SSHEnabled: true,
   141→					Meta:       nbpeer.PeerSystemMeta{GoOS: "linux", WtVersion: "0.0.1"},
   142→				},
   143→			},
   144→			Policies: []*types.Policy{
   145→				{
   146→					Rules: []*types.PolicyRule{
   147→						{
   148→							Bidirectional: true,
   149→							Protocol:      types.PolicyRuleProtocolTCP,
   150→						},
   151→					},
   152→				},
   153→				{
   154→					Rules: []*types.PolicyRule{
   155→						{
   156→							Bidirectional: false,
   157→							Protocol:      types.PolicyRuleProtocolTCP,
   158→						},
   159→					},
   160→				},
   161→			},
   162→			Routes: map[route.ID]*route.Route{
   163→				"1": {
   164→					ID:         "1",
   165→					PeerGroups: make([]string, 1),
   166→				},
   167→			},
   168→			Users: map[string]*types.User{
   169→				"1": {
   170→					Id:            "1",
   171→					IsServiceUser: true,
   172→					PATs: map[string]*types.PersonalAccessToken{
   173→						"1": {},
   174→					},
   175→				},
   176→				idpUserID: {
   177→					Id:            idpUserID,
   178→					IsServiceUser: false,
   179→					PATs: map[string]*types.PersonalAccessToken{
   180→						"1": {},
   181→					},
   182→				},
   183→			},
   184→			Networks: []*networkTypes.Network{
   185→				{
   186→					ID:        "1",
   187→					AccountID: "1",
   188→				},
   189→			},
   190→			NetworkResources: []*resourceTypes.NetworkResource{
   191→				{
   192→					ID:        "1",
   193→					AccountID: "1",
   194→					NetworkID: "1",
   195→				},
   196→				{
   197→					ID:        "2",
   198→					AccountID: "1",
   199→					NetworkID: "1",
   200→				},
   201→			},
   202→			NetworkRouters: []*routerTypes.NetworkRouter{
   203→				{
   204→					ID:        "1",
   205→					AccountID: "1",
   206→					NetworkID: "1",
   207→				},
   208→			},
   209→		},
   210→	}
   211→}
   212→
   213→// GetStoreEngine returns FileStoreEngine
   214→func (mockDatasource) GetStoreEngine() types.Engine {
   215→	return types.FileStoreEngine
   216→}
   217→
   218→// TestGenerateProperties tests and validate the properties generation by using the mockDatasource for the Worker.generateProperties
   219→func TestGenerateProperties(t *testing.T) {
   220→	ds := mockDatasource{}
   221→	worker := Worker{
   222→		dataSource:  ds,
   223→		connManager: ds,
   224→		idpManager:  EmbeddedType,
   225→	}
   226→
   227→	properties := worker.generateProperties(context.Background())
   228→
   229→	if properties["accounts"] != 2 {
   230→		t.Errorf("expected 2 accounts, got %d", properties["accounts"])
   231→	}
   232→	if properties["peers"] != 2 {
   233→		t.Errorf("expected 2 peers, got %d", properties["peers"])
   234→	}
   235→	if properties["routes"] != 2 {
   236→		t.Errorf("expected 2 routes, got %d", properties["routes"])
   237→	}
   238→	if properties["networks"] != 1 {
   239→		t.Errorf("expected 1 networks, got %d", properties["networks"])
   240→	}
   241→	if properties["network_resources"] != 2 {
   242→		t.Errorf("expected 2 network_resources, got %d", properties["network_resources"])
   243→	}
   244→	if properties["network_routers"] != 1 {
   245→		t.Errorf("expected 1 network_routers, got %d", properties["network_routers"])
   246→	}
   247→	if properties["rules"] != 4 {
   248→		t.Errorf("expected 4 rules, got %d", properties["rules"])
   249→	}
   250→	if properties["users"] != 2 {
   251→		t.Errorf("expected 1 users, got %d", properties["users"])
   252→	}
   253→	if properties["setup_keys_usage"] != 2 {
   254→		t.Errorf("expected 1 setup_keys_usage, got %d", properties["setup_keys_usage"])
   255→	}
   256→	if properties["pats"] != 4 {
   257→		t.Errorf("expected 4 personal_access_tokens, got %d", properties["pats"])
   258→	}
   259→	if properties["peers_ssh_enabled"] != 2 {
   260→		t.Errorf("expected 2 peers_ssh_enabled, got %d", properties["peers_ssh_enabled"])
   261→	}
   262→	if properties["routes_with_routing_groups"] != 2 {
   263→		t.Errorf("expected 2 routes_with_routing_groups, got %d", properties["routes_with_routing_groups"])
   264→	}
   265→	if properties["rules_protocol_tcp"] != 4 {
   266→		t.Errorf("expected 4 rules_protocol_tcp, got %d", properties["rules_protocol_tcp"])
   267→	}
   268→	if properties["rules_direction_oneway"] != 2 {
   269→		t.Errorf("expected 2 rules_direction_oneway, got %d", properties["rules_direction_oneway"])
   270→	}
   271→
   272→	if properties["active_peers_last_day"] != 2 {
   273→		t.Errorf("expected 2 active_peers_last_day, got %d", properties["active_peers_last_day"])
   274→	}
   275→	if properties["min_active_peer_version"] != "0.0.1" {
   276→		t.Errorf("expected 0.0.1 min_active_peer_version, got %s", properties["min_active_peer_version"])
   277→	}
   278→	if properties["max_active_peer_version"] != "0.0.1" {
   279→		t.Errorf("expected 0.0.1 max_active_peer_version, got %s", properties["max_active_peer_version"])
   280→	}
   281→
   282→	if properties["peers_login_expiration_enabled"] != 2 {
   283→		t.Errorf("expected 2 peers_login_expiration_enabled, got %d", properties["peers_login_expiration_enabled"])
   284→	}
   285→
   286→	if properties["service_users"] != 2 {
   287→		t.Errorf("expected 2 service_users, got %d", properties["service_users"])
   288→	}
   289→
   290→	if properties["peer_os_linux"] != 2 {
   291→		t.Errorf("expected 2 peer_os_linux, got %d", properties["peer_os_linux"])
   292→	}
   293→
   294→	if properties["ephemeral_peers_setup_keys"] != 2 {
   295→		t.Errorf("expected 2 ephemeral_peers_setup_keys, got %d", properties["ephemeral_peers_setup_keys_usage"])
   296→	}
   297→
   298→	if properties["ephemeral_peers_setup_keys_usage"] != 2 {
   299→		t.Errorf("expected 2 ephemeral_peers_setup_keys_usage, got %d", properties["ephemeral_peers_setup_keys_usage"])
   300→	}
   301→
   302→	if properties["nameservers"] != 2 {
   303→		t.Errorf("expected 2 nameservers, got %d", properties["nameservers"])
   304→	}
   305→
   306→	if properties["groups"] != 4 {
   307→		t.Errorf("expected 4 groups, got %d", properties["groups"])
   308→	}
   309→
   310→	if properties["user_peers"] != 2 {
   311→		t.Errorf("expected 2 user_peers, got %d", properties["user_peers"])
   312→	}
   313→
   314→	if properties["store_engine"] != types.FileStoreEngine {
   315→		t.Errorf("expected JsonFile, got %s", properties["store_engine"])
   316→	}
   317→
   318→	if properties["rules_with_src_posture_checks"] != 1 {
   319→		t.Errorf("expected 1 rules_with_src_posture_checks, got %d", properties["rules_with_src_posture_checks"])
   320→	}
   321→
   322→	if properties["posture_checks"] != 2 {
   323→		t.Errorf("expected 2 posture_checks, got %d", properties["posture_checks"])
   324→	}
   325→
   326→	if properties["rosenpass_enabled"] != 1 {
   327→		t.Errorf("expected 1 rosenpass_enabled, got %d", properties["rosenpass_enabled"])
   328→	}
   329→
   330→	if properties["active_user_peers_last_day"] != 2 {
   331→		t.Errorf("expected 2 active_user_peers_last_day, got %d", properties["active_user_peers_last_day"])
   332→	}
   333→
   334→	if properties["active_users_last_day"] != 1 {
   335→		t.Errorf("expected 1 active_users_last_day, got %d", properties["active_users_last_day"])
   336→	}
   337→
   338→	if properties["local_users_count"] != 1 {
   339→		t.Errorf("expected 1 local_users_count, got %d", properties["local_users_count"])
   340→	}
   341→	if properties["idp_users_count"] != 1 {
   342→		t.Errorf("expected 1 idp_users_count, got %d", properties["idp_users_count"])
   343→	}
   344→}
   345→

package metrics

import (
	"context"
	"testing"

	nbdns "github.com/netbirdio/netbird/dns"
	"github.com/netbirdio/netbird/idp/dex"
	resourceTypes "github.com/netbirdio/netbird/management/server/networks/resources/types"
	routerTypes "github.com/netbirdio/netbird/management/server/networks/routers/types"
	networkTypes "github.com/netbirdio/netbird/management/server/networks/types"
	nbpeer "github.com/netbirdio/netbird/management/server/peer"
	"github.com/netbirdio/netbird/management/server/posture"
	"github.com/netbirdio/netbird/management/server/types"
	"github.com/netbirdio/netbird/route"
)

type mockDatasource struct{}

// GetAllConnectedPeers returns a map of connected peer IDs for use in tests with predefined information
func (mockDatasource) GetAllConnectedPeers() map[string]struct{} {
	return map[string]struct{}{
		"1": {},
	}
}

// GetAllAccounts returns a list of *server.Account for use in tests with predefined information
func (mockDatasource) GetAllAccounts(_ context.Context) []*types.Account {
	localUserID := dex.EncodeDexUserID("10", "local")
	idpUserID := dex.EncodeDexUserID("20", "zitadel")
	return []*types.Account{
		{
			Id:       "1",
			Settings: &types.Settings{PeerLoginExpirationEnabled: true},
			SetupKeys: map[string]*types.SetupKey{
				"1": {
					Id:        "1",
					Ephemeral: true,
					UsedTimes: 1,
				},
			},
			Groups: map[string]*types.Group{
				"1": {},
				"2": {},
			},
			NameServerGroups: map[string]*nbdns.NameServerGroup{
				"1": {},
			},
			Peers: map[string]*nbpeer.Peer{
				"1": {
					ID:         "1",
					UserID:     "test",
					SSHEnabled: false,
					Meta:       nbpeer.PeerSystemMeta{GoOS: "linux", WtVersion: "0.0.1", Flags: nbpeer.Flags{ServerSSHAllowed: true, RosenpassEnabled: true}},
				},
			},
			Policies: []*types.Policy{
				{
					Rules: []*types.PolicyRule{
						{
							Bidirectional: true,
							Protocol:      types.PolicyRuleProtocolTCP,
						},
					},
				},
				{
					Rules: []*types.PolicyRule{
						{
							Bidirectional: false,
							Protocol:      types.PolicyRuleProtocolTCP,
						},
					},
					SourcePostureChecks: []string{"1"},
				},
			},
			Routes: map[route.ID]*route.Route{
				"1": {
					ID:         "1",
					PeerGroups: make([]string, 1),
				},
			},
			PostureChecks: []*posture.Checks{
				{
					ID:   "1",
					Name: "test",
					Checks: posture.ChecksDefinition{
						NBVersionCheck: &posture.NBVersionCheck{
							MinVersion: "0.0.1",
						},
					},
				},
				{
					ID:   "2",
					Name: "tes2",
					Checks: posture.ChecksDefinition{
						NBVersionCheck: &posture.NBVersionCheck{
							MinVersion: "0.0.2",
						},
					},
				},
			},
			Users: map[string]*types.User{
				"1": {
					Id:            "1",
					IsServiceUser: true,
					PATs: map[string]*types.PersonalAccessToken{
						"1": {},
					},
				},
				localUserID: {
					Id:            localUserID,
					IsServiceUser: false,
					PATs: map[string]*types.PersonalAccessToken{
						"1": {},
					},
				},
			},
		},
		{
			Id:       "2",
			Settings: &types.Settings{PeerLoginExpirationEnabled: true},
			SetupKeys: map[string]*types.SetupKey{
				"1": {
					Id:        "1",
					Ephemeral: true,
					UsedTimes: 1,
				},
			},
			Groups: map[string]*types.Group{
				"1": {},
				"2": {},
			},
			NameServerGroups: map[string]*nbdns.NameServerGroup{
				"1": {},
			},
			Peers: map[string]*nbpeer.Peer{
				"1": {
					ID:         "1",
					UserID:     "test",
					SSHEnabled: true,
					Meta:       nbpeer.PeerSystemMeta{GoOS: "linux", WtVersion: "0.0.1"},
				},
			},
			Policies: []*types.Policy{
				{
					Rules: []*types.PolicyRule{
						{
							Bidirectional: true,
							Protocol:      types.PolicyRuleProtocolTCP,
						},
					},
				},
				{
					Rules: []*types.PolicyRule{
						{
							Bidirectional: false,
							Protocol:      types.PolicyRuleProtocolTCP,
						},
					},
				},
			},
			Routes: map[route.ID]*route.Route{
				"1": {
					ID:         "1",
					PeerGroups: make([]string, 1),
				},
			},
			Users: map[string]*types.User{
				"1": {
					Id:            "1",
					IsServiceUser: true,
					PATs: map[string]*types.PersonalAccessToken{
						"1": {},
					},
				},
				idpUserID: {
					Id:            idpUserID,
					IsServiceUser: false,
					PATs: map[string]*types.PersonalAccessToken{
						"1": {},
					},
				},
			},
			Networks: []*networkTypes.Network{
				{
					ID:        "1",
					AccountID: "1",
				},
			},
			NetworkResources: []*resourceTypes.NetworkResource{
				{
					ID:        "1",
					AccountID: "1",
					NetworkID: "1",
				},
				{
					ID:        "2",
					AccountID: "1",
					NetworkID: "1",
				},
			},
			NetworkRouters: []*routerTypes.NetworkRouter{
				{
					ID:        "1",
					AccountID: "1",
					NetworkID: "1",
				},
			},
		},
	}
}

// GetStoreEngine returns FileStoreEngine
func (mockDatasource) GetStoreEngine() types.Engine {
	return types.FileStoreEngine
}

// TestGenerateProperties tests and validate the properties generation by using the mockDatasource for the Worker.generateProperties
func TestGenerateProperties(t *testing.T) {
	ds := mockDatasource{}
	worker := Worker{
		dataSource:  ds,
		connManager: ds,
		idpManager:  EmbeddedType,
	}

	properties := worker.generateProperties(context.Background())

	if properties["accounts"] != 2 {
		t.Errorf("expected 2 accounts, got %d", properties["accounts"])
	}
	if properties["peers"] != 2 {
		t.Errorf("expected 2 peers, got %d", properties["peers"])
	}
	if properties["routes"] != 2 {
		t.Errorf("expected 2 routes, got %d", properties["routes"])
	}
	if properties["networks"] != 1 {
		t.Errorf("expected 1 networks, got %d", properties["networks"])
	}
	if properties["network_resources"] != 2 {
		t.Errorf("expected 2 network_resources, got %d", properties["network_resources"])
	}
	if properties["network_routers"] != 1 {
		t.Errorf("expected 1 network_routers, got %d", properties["network_routers"])
	}
	if properties["rules"] != 4 {
		t.Errorf("expected 4 rules, got %d", properties["rules"])
	}
	if properties["users"] != 2 {
		t.Errorf("expected 1 users, got %d", properties["users"])
	}
	if properties["setup_keys_usage"] != 2 {
		t.Errorf("expected 1 setup_keys_usage, got %d", properties["setup_keys_usage"])
	}
	if properties["pats"] != 4 {
		t.Errorf("expected 4 personal_access_tokens, got %d", properties["pats"])
	}
	if properties["peers_ssh_enabled"] != 2 {
		t.Errorf("expected 2 peers_ssh_enabled, got %d", properties["peers_ssh_enabled"])
	}
	if properties["routes_with_routing_groups"] != 2 {
		t.Errorf("expected 2 routes_with_routing_groups, got %d", properties["routes_with_routing_groups"])
	}
	if properties["rules_protocol_tcp"] != 4 {
		t.Errorf("expected 4 rules_protocol_tcp, got %d", properties["rules_protocol_tcp"])
	}
	if properties["rules_direction_oneway"] != 2 {
		t.Errorf("expected 2 rules_direction_oneway, got %d", properties["rules_direction_oneway"])
	}

	if properties["active_peers_last_day"] != 2 {
		t.Errorf("expected 2 active_peers_last_day, got %d", properties["active_peers_last_day"])
	}
	if properties["min_active_peer_version"] != "0.0.1" {
		t.Errorf("expected 0.0.1 min_active_peer_version, got %s", properties["min_active_peer_version"])
	}
	if properties["max_active_peer_version"] != "0.0.1" {
		t.Errorf("expected 0.0.1 max_active_peer_version, got %s", properties["max_active_peer_version"])
	}

	if properties["peers_login_expiration_enabled"] != 2 {
		t.Errorf("expected 2 peers_login_expiration_enabled, got %d", properties["peers_login_expiration_enabled"])
	}

	if properties["service_users"] != 2 {
		t.Errorf("expected 2 service_users, got %d", properties["service_users"])
	}

	if properties["peer_os_linux"] != 2 {
		t.Errorf("expected 2 peer_os_linux, got %d", properties["peer_os_linux"])
	}

	if properties["ephemeral_peers_setup_keys"] != 2 {
		t.Errorf("expected 2 ephemeral_peers_setup_keys, got %d", properties["ephemeral_peers_setup_keys_usage"])
	}

	if properties["ephemeral_peers_setup_keys_usage"] != 2 {
		t.Errorf("expected 2 ephemeral_peers_setup_keys_usage, got %d", properties["ephemeral_peers_setup_keys_usage"])
	}

	if properties["nameservers"] != 2 {
		t.Errorf("expected 2 nameservers, got %d", properties["nameservers"])
	}

	if properties["groups"] != 4 {
		t.Errorf("expected 4 groups, got %d", properties["groups"])
	}

	if properties["user_peers"] != 2 {
		t.Errorf("expected 2 user_peers, got %d", properties["user_peers"])
	}

	if properties["store_engine"] != types.FileStoreEngine {
		t.Errorf("expected JsonFile, got %s", properties["store_engine"])
	}

	if properties["rules_with_src_posture_checks"] != 1 {
		t.Errorf("expected 1 rules_with_src_posture_checks, got %d", properties["rules_with_src_posture_checks"])
	}

	if properties["posture_checks"] != 2 {
		t.Errorf("expected 2 posture_checks, got %d", properties["posture_checks"])
	}

	if properties["rosenpass_enabled"] != 1 {
		t.Errorf("expected 1 rosenpass_enabled, got %d", properties["rosenpass_enabled"])
	}

	if properties["active_user_peers_last_day"] != 2 {
		t.Errorf("expected 2 active_user_peers_last_day, got %d", properties["active_user_peers_last_day"])
	}

	if properties["active_users_last_day"] != 1 {
		t.Errorf("expected 1 active_users_last_day, got %d", properties["active_users_last_day"])
	}

	if properties["local_users_count"] != 1 {
		t.Errorf("expected 1 local_users_count, got %d", properties["local_users_count"])
	}
	if properties["idp_users_count"] != 1 {
		t.Errorf("expected 1 idp_users_count, got %d", properties["idp_users_count"])
	}
}


The test data already has a `"zitadel"` connector user (line 30). I need to:
1. Add assertions for the new `embedded_idp_users_*` metric
2. Add a test for `extractIdpType`


Edit

	if properties["local_users_count"] != 1 {
		t.Errorf("expected 1 local_users_count, got %d", properties["local_users_count"])
	}
	if properties["idp_users_count"] != 1 {
		t.Errorf("expected 1 idp_users_count, got %d", properties["idp_users_count"])
	}
}

	if properties["local_users_count"] != 1 {
		t.Errorf("expected 1 local_users_count, got %d", properties["local_users_count"])
	}
	if properties["idp_users_count"] != 1 {
		t.Errorf("expected 1 idp_users_count, got %d", properties["idp_users_count"])
	}
	if properties["embedded_idp_users_zitadel"] != 1 {
		t.Errorf("expected 1 embedded_idp_users_zitadel, got %v", properties["embedded_idp_users_zitadel"])
	}
}

func TestExtractIdpType(t *testing.T) {
	tests := []struct {
		connectorID string
		expected    string
	}{
		{"okta-abc123def", "okta"},
		{"zitadel-d5uv82dra0haedlf6kv0", "zitadel"},
		{"entra-xyz789", "entra"},
		{"google-abc123", "google"},
		{"pocketid-abc123", "pocketid"},
		{"microsoft-abc123", "microsoft"},
		{"authentik-abc123", "authentik"},
		{"keycloak-d5uv82dra0haedlf6kv0", "keycloak"},
		{"local", "oidc"},
		{"", "oidc"},
	}

	for _, tt := range tests {
		t.Run(tt.connectorID, func(t *testing.T) {
			result := extractIdpType(tt.connectorID)
			if result != tt.expected {
				t.Errorf("extractIdpType(%q) = %q, want %q", tt.connectorID, result, tt.expected)
			}
		})
	}
}


The file /Users/misha/Documents/GolandProjects/netbird/netbird/management/server/metrics/selfhosted_test.go has been updated successfully.

package metrics

import (
	"context"
	"testing"

	nbdns "github.com/netbirdio/netbird/dns"
	"github.com/netbirdio/netbird/idp/dex"
	resourceTypes "github.com/netbirdio/netbird/management/server/networks/resources/types"
	routerTypes "github.com/netbirdio/netbird/management/server/networks/routers/types"
	networkTypes "github.com/netbirdio/netbird/management/server/networks/types"
	nbpeer "github.com/netbirdio/netbird/management/server/peer"
	"github.com/netbirdio/netbird/management/server/posture"
	"github.com/netbirdio/netbird/management/server/types"
	"github.com/netbirdio/netbird/route"
)

type mockDatasource struct{}

// GetAllConnectedPeers returns a map of connected peer IDs for use in tests with predefined information
func (mockDatasource) GetAllConnectedPeers() map[string]struct{} {
	return map[string]struct{}{
		"1": {},
	}
}

// GetAllAccounts returns a list of *server.Account for use in tests with predefined information
func (mockDatasource) GetAllAccounts(_ context.Context) []*types.Account {
	localUserID := dex.EncodeDexUserID("10", "local")
	idpUserID := dex.EncodeDexUserID("20", "zitadel")
	return []*types.Account{
		{
			Id:       "1",
			Settings: &types.Settings{PeerLoginExpirationEnabled: true},
			SetupKeys: map[string]*types.SetupKey{
				"1": {
					Id:        "1",
					Ephemeral: true,
					UsedTimes: 1,
				},
			},
			Groups: map[string]*types.Group{
				"1": {},
				"2": {},
			},
			NameServerGroups: map[string]*nbdns.NameServerGroup{
				"1": {},
			},
			Peers: map[string]*nbpeer.Peer{
				"1": {
					ID:         "1",
					UserID:     "test",
					SSHEnabled: false,
					Meta:       nbpeer.PeerSystemMeta{GoOS: "linux", WtVersion: "0.0.1", Flags: nbpeer.Flags{ServerSSHAllowed: true, RosenpassEnabled: true}},
				},
			},
			Policies: []*types.Policy{
				{
					Rules: []*types.PolicyRule{
						{
							Bidirectional: true,
							Protocol:      types.PolicyRuleProtocolTCP,
						},
					},
				},
				{
					Rules: []*types.PolicyRule{
						{
							Bidirectional: false,
							Protocol:      types.PolicyRuleProtocolTCP,
						},
					},
					SourcePostureChecks: []string{"1"},
				},
			},
			Routes: map[route.ID]*route.Route{
				"1": {
					ID:         "1",
					PeerGroups: make([]string, 1),
				},
			},
			PostureChecks: []*posture.Checks{
				{
					ID:   "1",
					Name: "test",
					Checks: posture.ChecksDefinition{
						NBVersionCheck: &posture.NBVersionCheck{
							MinVersion: "0.0.1",
						},
					},
				},
				{
					ID:   "2",
					Name: "tes2",
					Checks: posture.ChecksDefinition{
						NBVersionCheck: &posture.NBVersionCheck{
							MinVersion: "0.0.2",
						},
					},
				},
			},
			Users: map[string]*types.User{
				"1": {
					Id:            "1",
					IsServiceUser: true,
					PATs: map[string]*types.PersonalAccessToken{
						"1": {},
					},
				},
				localUserID: {
					Id:            localUserID,
					IsServiceUser: false,
					PATs: map[string]*types.PersonalAccessToken{
						"1": {},
					},
				},
			},
		},
		{
			Id:       "2",
			Settings: &types.Settings{PeerLoginExpirationEnabled: true},
			SetupKeys: map[string]*types.SetupKey{
				"1": {
					Id:        "1",
					Ephemeral: true,
					UsedTimes: 1,
				},
			},
			Groups: map[string]*types.Group{
				"1": {},
				"2": {},
			},
			NameServerGroups: map[string]*nbdns.NameServerGroup{
				"1": {},
			},
			Peers: map[string]*nbpeer.Peer{
				"1": {
					ID:         "1",
					UserID:     "test",
					SSHEnabled: true,
					Meta:       nbpeer.PeerSystemMeta{GoOS: "linux", WtVersion: "0.0.1"},
				},
			},
			Policies: []*types.Policy{
				{
					Rules: []*types.PolicyRule{
						{
							Bidirectional: true,
							Protocol:      types.PolicyRuleProtocolTCP,
						},
					},
				},
				{
					Rules: []*types.PolicyRule{
						{
							Bidirectional: false,
							Protocol:      types.PolicyRuleProtocolTCP,
						},
					},
				},
			},
			Routes: map[route.ID]*route.Route{
				"1": {
					ID:         "1",
					PeerGroups: make([]string, 1),
				},
			},
			Users: map[string]*types.User{
				"1": {
					Id:            "1",
					IsServiceUser: true,
					PATs: map[string]*types.PersonalAccessToken{
						"1": {},
					},
				},
				idpUserID: {
					Id:            idpUserID,
					IsServiceUser: false,
					PATs: map[string]*types.PersonalAccessToken{
						"1": {},
					},
				},
			},
			Networks: []*networkTypes.Network{
				{
					ID:        "1",
					AccountID: "1",
				},
			},
			NetworkResources: []*resourceTypes.NetworkResource{
				{
					ID:        "1",
					AccountID: "1",
					NetworkID: "1",
				},
				{
					ID:        "2",
					AccountID: "1",
					NetworkID: "1",
				},
			},
			NetworkRouters: []*routerTypes.NetworkRouter{
				{
					ID:        "1",
					AccountID: "1",
					NetworkID: "1",
				},
			},
		},
	}
}

// GetStoreEngine returns FileStoreEngine
func (mockDatasource) GetStoreEngine() types.Engine {
	return types.FileStoreEngine
}

// TestGenerateProperties tests and validate the properties generation by using the mockDatasource for the Worker.generateProperties
func TestGenerateProperties(t *testing.T) {
	ds := mockDatasource{}
	worker := Worker{
		dataSource:  ds,
		connManager: ds,
		idpManager:  EmbeddedType,
	}

	properties := worker.generateProperties(context.Background())

	if properties["accounts"] != 2 {
		t.Errorf("expected 2 accounts, got %d", properties["accounts"])
	}
	if properties["peers"] != 2 {
		t.Errorf("expected 2 peers, got %d", properties["peers"])
	}
	if properties["routes"] != 2 {
		t.Errorf("expected 2 routes, got %d", properties["routes"])
	}
	if properties["networks"] != 1 {
		t.Errorf("expected 1 networks, got %d", properties["networks"])
	}
	if properties["network_resources"] != 2 {
		t.Errorf("expected 2 network_resources, got %d", properties["network_resources"])
	}
	if properties["network_routers"] != 1 {
		t.Errorf("expected 1 network_routers, got %d", properties["network_routers"])
	}
	if properties["rules"] != 4 {
		t.Errorf("expected 4 rules, got %d", properties["rules"])
	}
	if properties["users"] != 2 {
		t.Errorf("expected 1 users, got %d", properties["users"])
	}
	if properties["setup_keys_usage"] != 2 {
		t.Errorf("expected 1 setup_keys_usage, got %d", properties["setup_keys_usage"])
	}
	if properties["pats"] != 4 {
		t.Errorf("expected 4 personal_access_tokens, got %d", properties["pats"])
	}
	if properties["peers_ssh_enabled"] != 2 {
		t.Errorf("expected 2 peers_ssh_enabled, got %d", properties["peers_ssh_enabled"])
	}
	if properties["routes_with_routing_groups"] != 2 {
		t.Errorf("expected 2 routes_with_routing_groups, got %d", properties["routes_with_routing_groups"])
	}
	if properties["rules_protocol_tcp"] != 4 {
		t.Errorf("expected 4 rules_protocol_tcp, got %d", properties["rules_protocol_tcp"])
	}
	if properties["rules_direction_oneway"] != 2 {
		t.Errorf("expected 2 rules_direction_oneway, got %d", properties["rules_direction_oneway"])
	}

	if properties["active_peers_last_day"] != 2 {
		t.Errorf("expected 2 active_peers_last_day, got %d", properties["active_peers_last_day"])
	}
	if properties["min_active_peer_version"] != "0.0.1" {
		t.Errorf("expected 0.0.1 min_active_peer_version, got %s", properties["min_active_peer_version"])
	}
	if properties["max_active_peer_version"] != "0.0.1" {
		t.Errorf("expected 0.0.1 max_active_peer_version, got %s", properties["max_active_peer_version"])
	}

	if properties["peers_login_expiration_enabled"] != 2 {
		t.Errorf("expected 2 peers_login_expiration_enabled, got %d", properties["peers_login_expiration_enabled"])
	}

	if properties["service_users"] != 2 {
		t.Errorf("expected 2 service_users, got %d", properties["service_users"])
	}

	if properties["peer_os_linux"] != 2 {
		t.Errorf("expected 2 peer_os_linux, got %d", properties["peer_os_linux"])
	}

	if properties["ephemeral_peers_setup_keys"] != 2 {
		t.Errorf("expected 2 ephemeral_peers_setup_keys, got %d", properties["ephemeral_peers_setup_keys_usage"])
	}

	if properties["ephemeral_peers_setup_keys_usage"] != 2 {
		t.Errorf("expected 2 ephemeral_peers_setup_keys_usage, got %d", properties["ephemeral_peers_setup_keys_usage"])
	}

	if properties["nameservers"] != 2 {
		t.Errorf("expected 2 nameservers, got %d", properties["nameservers"])
	}

	if properties["groups"] != 4 {
		t.Errorf("expected 4 groups, got %d", properties["groups"])
	}

	if properties["user_peers"] != 2 {
		t.Errorf("expected 2 user_peers, got %d", properties["user_peers"])
	}

	if properties["store_engine"] != types.FileStoreEngine {
		t.Errorf("expected JsonFile, got %s", properties["store_engine"])
	}

	if properties["rules_with_src_posture_checks"] != 1 {
		t.Errorf("expected 1 rules_with_src_posture_checks, got %d", properties["rules_with_src_posture_checks"])
	}

	if properties["posture_checks"] != 2 {
		t.Errorf("expected 2 posture_checks, got %d", properties["posture_checks"])
	}

	if properties["rosenpass_enabled"] != 1 {
		t.Errorf("expected 1 rosenpass_enabled, got %d", properties["rosenpass_enabled"])
	}

	if properties["active_user_peers_last_day"] != 2 {
		t.Errorf("expected 2 active_user_peers_last_day, got %d", properties["active_user_peers_last_day"])
	}

	if properties["active_users_last_day"] != 1 {
		t.Errorf("expected 1 active_users_last_day, got %d", properties["active_users_last_day"])
	}

	if properties["local_users_count"] != 1 {
		t.Errorf("expected 1 local_users_count, got %d", properties["local_users_count"])
	}
	if properties["idp_users_count"] != 1 {
		t.Errorf("expected 1 idp_users_count, got %d", properties["idp_users_count"])
	}
}

package metrics

import (
	"context"
	"testing"

	nbdns "github.com/netbirdio/netbird/dns"
	"github.com/netbirdio/netbird/idp/dex"
	resourceTypes "github.com/netbirdio/netbird/management/server/networks/resources/types"
	routerTypes "github.com/netbirdio/netbird/management/server/networks/routers/types"
	networkTypes "github.com/netbirdio/netbird/management/server/networks/types"
	nbpeer "github.com/netbirdio/netbird/management/server/peer"
	"github.com/netbirdio/netbird/management/server/posture"
	"github.com/netbirdio/netbird/management/server/types"
	"github.com/netbirdio/netbird/route"
)

type mockDatasource struct{}

// GetAllConnectedPeers returns a map of connected peer IDs for use in tests with predefined information
func (mockDatasource) GetAllConnectedPeers() map[string]struct{} {
	return map[string]struct{}{
		"1": {},
	}
}

// GetAllAccounts returns a list of *server.Account for use in tests with predefined information
func (mockDatasource) GetAllAccounts(_ context.Context) []*types.Account {
	localUserID := dex.EncodeDexUserID("10", "local")
	idpUserID := dex.EncodeDexUserID("20", "zitadel")
	return []*types.Account{
		{
			Id:       "1",
			Settings: &types.Settings{PeerLoginExpirationEnabled: true},
			SetupKeys: map[string]*types.SetupKey{
				"1": {
					Id:        "1",
					Ephemeral: true,
					UsedTimes: 1,
				},
			},
			Groups: map[string]*types.Group{
				"1": {},
				"2": {},
			},
			NameServerGroups: map[string]*nbdns.NameServerGroup{
				"1": {},
			},
			Peers: map[string]*nbpeer.Peer{
				"1": {
					ID:         "1",
					UserID:     "test",
					SSHEnabled: false,
					Meta:       nbpeer.PeerSystemMeta{GoOS: "linux", WtVersion: "0.0.1", Flags: nbpeer.Flags{ServerSSHAllowed: true, RosenpassEnabled: true}},
				},
			},
			Policies: []*types.Policy{
				{
					Rules: []*types.PolicyRule{
						{
							Bidirectional: true,
							Protocol:      types.PolicyRuleProtocolTCP,
						},
					},
				},
				{
					Rules: []*types.PolicyRule{
						{
							Bidirectional: false,
							Protocol:      types.PolicyRuleProtocolTCP,
						},
					},
					SourcePostureChecks: []string{"1"},
				},
			},
			Routes: map[route.ID]*route.Route{
				"1": {
					ID:         "1",
					PeerGroups: make([]string, 1),
				},
			},
			PostureChecks: []*posture.Checks{
				{
					ID:   "1",
					Name: "test",
					Checks: posture.ChecksDefinition{
						NBVersionCheck: &posture.NBVersionCheck{
							MinVersion: "0.0.1",
						},
					},
				},
				{
					ID:   "2",
					Name: "tes2",
					Checks: posture.ChecksDefinition{
						NBVersionCheck: &posture.NBVersionCheck{
							MinVersion: "0.0.2",
						},
					},
				},
			},
			Users: map[string]*types.User{
				"1": {
					Id:            "1",
					IsServiceUser: true,
					PATs: map[string]*types.PersonalAccessToken{
						"1": {},
					},
				},
				localUserID: {
					Id:            localUserID,
					IsServiceUser: false,
					PATs: map[string]*types.PersonalAccessToken{
						"1": {},
					},
				},
			},
		},
		{
			Id:       "2",
			Settings: &types.Settings{PeerLoginExpirationEnabled: true},
			SetupKeys: map[string]*types.SetupKey{
				"1": {
					Id:        "1",
					Ephemeral: true,
					UsedTimes: 1,
				},
			},
			Groups: map[string]*types.Group{
				"1": {},
				"2": {},
			},
			NameServerGroups: map[string]*nbdns.NameServerGroup{
				"1": {},
			},
			Peers: map[string]*nbpeer.Peer{
				"1": {
					ID:         "1",
					UserID:     "test",
					SSHEnabled: true,
					Meta:       nbpeer.PeerSystemMeta{GoOS: "linux", WtVersion: "0.0.1"},
				},
			},
			Policies: []*types.Policy{
				{
					Rules: []*types.PolicyRule{
						{
							Bidirectional: true,
							Protocol:      types.PolicyRuleProtocolTCP,
						},
					},
				},
				{
					Rules: []*types.PolicyRule{
						{
							Bidirectional: false,
							Protocol:      types.PolicyRuleProtocolTCP,
						},
					},
				},
			},
			Routes: map[route.ID]*route.Route{
				"1": {
					ID:         "1",
					PeerGroups: make([]string, 1),
				},
			},
			Users: map[string]*types.User{
				"1": {
					Id:            "1",
					IsServiceUser: true,
					PATs: map[string]*types.PersonalAccessToken{
						"1": {},
					},
				},
				idpUserID: {
					Id:            idpUserID,
					IsServiceUser: false,
					PATs: map[string]*types.PersonalAccessToken{
						"1": {},
					},
				},
			},
			Networks: []*networkTypes.Network{
				{
					ID:        "1",
					AccountID: "1",
				},
			},
			NetworkResources: []*resourceTypes.NetworkResource{
				{
					ID:        "1",
					AccountID: "1",
					NetworkID: "1",
				},
				{
					ID:        "2",
					AccountID: "1",
					NetworkID: "1",
				},
			},
			NetworkRouters: []*routerTypes.NetworkRouter{
				{
					ID:        "1",
					AccountID: "1",
					NetworkID: "1",
				},
			},
		},
	}
}

// GetStoreEngine returns FileStoreEngine
func (mockDatasource) GetStoreEngine() types.Engine {
	return types.FileStoreEngine
}

// TestGenerateProperties tests and validate the properties generation by using the mockDatasource for the Worker.generateProperties
func TestGenerateProperties(t *testing.T) {
	ds := mockDatasource{}
	worker := Worker{
		dataSource:  ds,
		connManager: ds,
		idpManager:  EmbeddedType,
	}

	properties := worker.generateProperties(context.Background())

	if properties["accounts"] != 2 {
		t.Errorf("expected 2 accounts, got %d", properties["accounts"])
	}
	if properties["peers"] != 2 {
		t.Errorf("expected 2 peers, got %d", properties["peers"])
	}
	if properties["routes"] != 2 {
		t.Errorf("expected 2 routes, got %d", properties["routes"])
	}
	if properties["networks"] != 1 {
		t.Errorf("expected 1 networks, got %d", properties["networks"])
	}
	if properties["network_resources"] != 2 {
		t.Errorf("expected 2 network_resources, got %d", properties["network_resources"])
	}
	if properties["network_routers"] != 1 {
		t.Errorf("expected 1 network_routers, got %d", properties["network_routers"])
	}
	if properties["rules"] != 4 {
		t.Errorf("expected 4 rules, got %d", properties["rules"])
	}
	if properties["users"] != 2 {
		t.Errorf("expected 1 users, got %d", properties["users"])
	}
	if properties["setup_keys_usage"] != 2 {
		t.Errorf("expected 1 setup_keys_usage, got %d", properties["setup_keys_usage"])
	}
	if properties["pats"] != 4 {
		t.Errorf("expected 4 personal_access_tokens, got %d", properties["pats"])
	}
	if properties["peers_ssh_enabled"] != 2 {
		t.Errorf("expected 2 peers_ssh_enabled, got %d", properties["peers_ssh_enabled"])
	}
	if properties["routes_with_routing_groups"] != 2 {
		t.Errorf("expected 2 routes_with_routing_groups, got %d", properties["routes_with_routing_groups"])
	}
	if properties["rules_protocol_tcp"] != 4 {
		t.Errorf("expected 4 rules_protocol_tcp, got %d", properties["rules_protocol_tcp"])
	}
	if properties["rules_direction_oneway"] != 2 {
		t.Errorf("expected 2 rules_direction_oneway, got %d", properties["rules_direction_oneway"])
	}

	if properties["active_peers_last_day"] != 2 {
		t.Errorf("expected 2 active_peers_last_day, got %d", properties["active_peers_last_day"])
	}
	if properties["min_active_peer_version"] != "0.0.1" {
		t.Errorf("expected 0.0.1 min_active_peer_version, got %s", properties["min_active_peer_version"])
	}
	if properties["max_active_peer_version"] != "0.0.1" {
		t.Errorf("expected 0.0.1 max_active_peer_version, got %s", properties["max_active_peer_version"])
	}

	if properties["peers_login_expiration_enabled"] != 2 {
		t.Errorf("expected 2 peers_login_expiration_enabled, got %d", properties["peers_login_expiration_enabled"])
	}

	if properties["service_users"] != 2 {
		t.Errorf("expected 2 service_users, got %d", properties["service_users"])
	}

	if properties["peer_os_linux"] != 2 {
		t.Errorf("expected 2 peer_os_linux, got %d", properties["peer_os_linux"])
	}

	if properties["ephemeral_peers_setup_keys"] != 2 {
		t.Errorf("expected 2 ephemeral_peers_setup_keys, got %d", properties["ephemeral_peers_setup_keys_usage"])
	}

	if properties["ephemeral_peers_setup_keys_usage"] != 2 {
		t.Errorf("expected 2 ephemeral_peers_setup_keys_usage, got %d", properties["ephemeral_peers_setup_keys_usage"])
	}

	if properties["nameservers"] != 2 {
		t.Errorf("expected 2 nameservers, got %d", properties["nameservers"])
	}

	if properties["groups"] != 4 {
		t.Errorf("expected 4 groups, got %d", properties["groups"])
	}

	if properties["user_peers"] != 2 {
		t.Errorf("expected 2 user_peers, got %d", properties["user_peers"])
	}

	if properties["store_engine"] != types.FileStoreEngine {
		t.Errorf("expected JsonFile, got %s", properties["store_engine"])
	}

	if properties["rules_with_src_posture_checks"] != 1 {
		t.Errorf("expected 1 rules_with_src_posture_checks, got %d", properties["rules_with_src_posture_checks"])
	}

	if properties["posture_checks"] != 2 {
		t.Errorf("expected 2 posture_checks, got %d", properties["posture_checks"])
	}

	if properties["rosenpass_enabled"] != 1 {
		t.Errorf("expected 1 rosenpass_enabled, got %d", properties["rosenpass_enabled"])
	}

	if properties["active_user_peers_last_day"] != 2 {
		t.Errorf("expected 2 active_user_peers_last_day, got %d", properties["active_user_peers_last_day"])
	}

	if properties["active_users_last_day"] != 1 {
		t.Errorf("expected 1 active_users_last_day, got %d", properties["active_users_last_day"])
	}

	if properties["local_users_count"] != 1 {
		t.Errorf("expected 1 local_users_count, got %d", properties["local_users_count"])
	}
	if properties["idp_users_count"] != 1 {
		t.Errorf("expected 1 idp_users_count, got %d", properties["idp_users_count"])
	}
	if properties["embedded_idp_users_zitadel"] != 1 {
		t.Errorf("expected 1 embedded_idp_users_zitadel, got %v", properties["embedded_idp_users_zitadel"])
	}
}

func TestExtractIdpType(t *testing.T) {
	tests := []struct {
		connectorID string
		expected    string
	}{
		{"okta-abc123def", "okta"},
		{"zitadel-d5uv82dra0haedlf6kv0", "zitadel"},
		{"entra-xyz789", "entra"},
		{"google-abc123", "google"},
		{"pocketid-abc123", "pocketid"},
		{"microsoft-abc123", "microsoft"},
		{"authentik-abc123", "authentik"},
		{"keycloak-d5uv82dra0haedlf6kv0", "keycloak"},
		{"local", "oidc"},
		{"", "oidc"},
	}

	for _, tt := range tests {
		t.Run(tt.connectorID, func(t *testing.T) {
			result := extractIdpType(tt.connectorID)
			if result != tt.expected {
				t.Errorf("extractIdpType(%q) = %q, want %q", tt.connectorID, result, tt.expected)
			}
		})
	}
}

+  if properties["embedded_idp_users_zitadel"] != 1 {

+    t.Errorf("expected 1 embedded_idp_users_zitadel, got %v", properties["embedded_idp_users_zitadel"])

+func TestExtractIdpType(t *testing.T) {

+    {"zitadel-d5uv82dra0haedlf6kv0", "zitadel"},

+    {"keycloak-d5uv82dra0haedlf6kv0", "keycloak"},

+    t.Run(tt.connectorID, func(t *testing.T) {

+      result := extractIdpType(tt.connectorID)

+        t.Errorf("extractIdpType(%q) = %q, want %q", tt.connectorID, result, tt.expected)


Bash

go test ./management/server/metrics/ -v -run "TestGenerateProperties|TestExtractIdpType" 2>&1

Run the metrics tests


Run the metrics tests


Output:
    --- PASS: TestExtractIdpType/#00 (0.00s)
FAIL
FAIL	github.com/netbirdio/netbird/management/server/metrics	2.342s
FAIL

=== RUN   TestGenerateProperties
    selfhosted_test.go:345: expected 1 embedded_idp_users_zitadel, got <nil>
--- FAIL: TestGenerateProperties (0.00s)
=== RUN   TestExtractIdpType
=== RUN   TestExtractIdpType/okta-abc123def
=== RUN   REDACTED
=== RUN   TestExtractIdpType/entra-xyz789
=== RUN   TestExtractIdpType/google-abc123
=== RUN   TestExtractIdpType/pocketid-abc123
=== RUN   TestExtractIdpType/microsoft-abc123
=== RUN   TestExtractIdpType/authentik-abc123
=== RUN   REDACTED
=== RUN   TestExtractIdpType/local
=== RUN   TestExtractIdpType/#00
--- PASS: TestExtractIdpType (0.00s)
    --- PASS: TestExtractIdpType/okta-abc123def (0.00s)
    --- PASS: REDACTED (0.00s)
    --- PASS: TestExtractIdpType/entra-xyz789 (0.00s)
    --- PASS: TestExtractIdpType/google-abc123 (0.00s)
    --- PASS: TestExtractIdpType/pocketid-abc123 (0.00s)
    --- PASS: TestExtractIdpType/microsoft-abc123 (0.00s)
    --- PASS: TestExtractIdpType/authentik-abc123 (0.00s)
    --- PASS: REDACTED (0.00s)
    --- PASS: TestExtractIdpType/local (0.00s)
    --- PASS: TestExtractIdpType/#00 (0.00s)
FAIL
FAIL	github.com/netbirdio/netbird/management/server/metrics	2.342s
FAIL


Error: Exit code 1
=== RUN   TestGenerateProperties
    selfhosted_test.go:345: expected 1 embedded_idp_users_zitadel, got <nil>
--- FAIL: TestGenerateProperties (0.00s)
=== RUN   TestExtractIdpType
=== RUN   TestExtractIdpType/okta-abc123def
=== RUN   REDACTED
=== RUN   TestExtractIdpType/entra-xyz789
=== RUN   TestExtractIdpType/google-abc123
=== RUN   TestExtractIdpType/pocketid-abc123
=== RUN   TestExtractIdpType/microsoft-abc123
=== RUN   TestExtractIdpType/authentik-abc123
=== RUN   REDACTED
=== RUN   TestExtractIdpType/local
=== RUN   TestExtractIdpType/#00
--- PASS: TestExtractIdpType (0.00s)
    --- PASS: TestExtractIdpType/okta-abc123def (0.00s)
    --- PASS: REDACTED (0.00s)
    --- PASS: TestExtractIdpType/entra-xyz789 (0.00s)
    --- PASS: TestExtractIdpType/google-abc123 (0.00s)
    --- PASS: TestExtractIdpType/pocketid-abc123 (0.00s)
    --- PASS: TestExtractIdpType/microsoft-abc123 (0.00s)
    --- PASS: TestExtractIdpType/authentik-abc123 (0.00s)
    --- PASS: REDACTED (0.00s)
    --- PASS: TestExtractIdpType/local (0.00s)
    --- PASS: TestExtractIdpType/#00 (0.00s)
FAIL
FAIL	github.com/netbirdio/netbird/management/server/metrics	2.342s
FAIL

=== RUN   TestGenerateProperties
    selfhosted_test.go:345: expected 1 embedded_idp_users_zitadel, got <nil>
--- FAIL: TestGenerateProperties (0.00s)
=== RUN   TestExtractIdpType
=== RUN   TestExtractIdpType/okta-abc123def
=== RUN   REDACTED
=== RUN   TestExtractIdpType/entra-xyz789
=== RUN   TestExtractIdpType/google-abc123
=== RUN   TestExtractIdpType/pocketid-abc123
=== RUN   TestExtractIdpType/microsoft-abc123
=== RUN   TestExtractIdpType/authentik-abc123
=== RUN   REDACTED
=== RUN   TestExtractIdpType/local
=== RUN   TestExtractIdpType/#00
--- PASS: TestExtractIdpType (0.00s)
    --- PASS: TestExtractIdpType/okta-abc123def (0.00s)
    --- PASS: REDACTED (0.00s)
    --- PASS: TestExtractIdpType/entra-xyz789 (0.00s)
    --- PASS: TestExtractIdpType/google-abc123 (0.00s)
    --- PASS: TestExtractIdpType/pocketid-abc123 (0.00s)
    --- PASS: TestExtractIdpType/microsoft-abc123 (0.00s)
    --- PASS: TestExtractIdpType/authentik-abc123 (0.00s)
    --- PASS: REDACTED (0.00s)
    --- PASS: TestExtractIdpType/local (0.00s)
    --- PASS: TestExtractIdpType/#00 (0.00s)
FAIL
FAIL	github.com/netbirdio/netbird/management/server/metrics	2.342s
FAIL


`TestExtractIdpType` passes. The `TestGenerateProperties` fails because the mock data uses `"zitadel"` as the raw connector ID (not the real format like `"zitadel-abc123"`). Let me check what the mock produces:

The mock at line 30 does `dex.EncodeDexUserID("20", "zitadel")` — the connector ID is just `"zitadel"` (no `-xid` suffix). So `extractIdpType("zitadel")` returns `"oidc"` since there's no `-` separator. I need to update the mock to use a realistic connector ID.


Edit

idpUserID := dex.EncodeDexUserID("20", "zitadel")

idpUserID := dex.EncodeDexUserID("20", "zitadel-d5uv82dra0haedlf6kv0")


package metrics

import (
	"context"
	"testing"

	nbdns "github.com/netbirdio/netbird/dns"
	"github.com/netbirdio/netbird/idp/dex"
	resourceTypes "github.com/netbirdio/netbird/management/server/networks/resources/types"
	routerTypes "github.com/netbirdio/netbird/management/server/networks/routers/types"
	networkTypes "github.com/netbirdio/netbird/management/server/networks/types"
	nbpeer "github.com/netbirdio/netbird/management/server/peer"
	"github.com/netbirdio/netbird/management/server/posture"
	"github.com/netbirdio/netbird/management/server/types"
	"github.com/netbirdio/netbird/route"
)

type mockDatasource struct{}

// GetAllConnectedPeers returns a map of connected peer IDs for use in tests with predefined information
func (mockDatasource) GetAllConnectedPeers() map[string]struct{} {
	return map[string]struct{}{
		"1": {},
	}
}

// GetAllAccounts returns a list of *server.Account for use in tests with predefined information
func (mockDatasource) GetAllAccounts(_ context.Context) []*types.Account {
	localUserID := dex.EncodeDexUserID("10", "local")
	idpUserID := dex.EncodeDexUserID("20", "zitadel")
	return []*types.Account{
		{
			Id:       "1",
			Settings: &types.Settings{PeerLoginExpirationEnabled: true},
			SetupKeys: map[string]*types.SetupKey{
				"1": {
					Id:        "1",
					Ephemeral: true,
					UsedTimes: 1,
				},
			},
			Groups: map[string]*types.Group{
				"1": {},
				"2": {},
			},
			NameServerGroups: map[string]*nbdns.NameServerGroup{
				"1": {},
			},
			Peers: map[string]*nbpeer.Peer{
				"1": {
					ID:         "1",
					UserID:     "test",
					SSHEnabled: false,
					Meta:       nbpeer.PeerSystemMeta{GoOS: "linux", WtVersion: "0.0.1", Flags: nbpeer.Flags{ServerSSHAllowed: true, RosenpassEnabled: true}},
				},
			},
			Policies: []*types.Policy{
				{
					Rules: []*types.PolicyRule{
						{
							Bidirectional: true,
							Protocol:      types.PolicyRuleProtocolTCP,
						},
					},
				},
				{
					Rules: []*types.PolicyRule{
						{
							Bidirectional: false,
							Protocol:      types.PolicyRuleProtocolTCP,
						},
					},
					SourcePostureChecks: []string{"1"},
				},
			},
			Routes: map[route.ID]*route.Route{
				"1": {
					ID:         "1",
					PeerGroups: make([]string, 1),
				},
			},
			PostureChecks: []*posture.Checks{
				{
					ID:   "1",
					Name: "test",
					Checks: posture.ChecksDefinition{
						NBVersionCheck: &posture.NBVersionCheck{
							MinVersion: "0.0.1",
						},
					},
				},
				{
					ID:   "2",
					Name: "tes2",
					Checks: posture.ChecksDefinition{
						NBVersionCheck: &posture.NBVersionCheck{
							MinVersion: "0.0.2",
						},
					},
				},
			},
			Users: map[string]*types.User{
				"1": {
					Id:            "1",
					IsServiceUser: true,
					PATs: map[string]*types.PersonalAccessToken{
						"1": {},
					},
				},
				localUserID: {
					Id:            localUserID,
					IsServiceUser: false,
					PATs: map[string]*types.PersonalAccessToken{
						"1": {},
					},
				},
			},
		},
		{
			Id:       "2",
			Settings: &types.Settings{PeerLoginExpirationEnabled: true},
			SetupKeys: map[string]*types.SetupKey{
				"1": {
					Id:        "1",
					Ephemeral: true,
					UsedTimes: 1,
				},
			},
			Groups: map[string]*types.Group{
				"1": {},
				"2": {},
			},
			NameServerGroups: map[string]*nbdns.NameServerGroup{
				"1": {},
			},
			Peers: map[string]*nbpeer.Peer{
				"1": {
					ID:         "1",
					UserID:     "test",
					SSHEnabled: true,
					Meta:       nbpeer.PeerSystemMeta{GoOS: "linux", WtVersion: "0.0.1"},
				},
			},
			Policies: []*types.Policy{
				{
					Rules: []*types.PolicyRule{
						{
							Bidirectional: true,
							Protocol:      types.PolicyRuleProtocolTCP,
						},
					},
				},
				{
					Rules: []*types.PolicyRule{
						{
							Bidirectional: false,
							Protocol:      types.PolicyRuleProtocolTCP,
						},
					},
				},
			},
			Routes: map[route.ID]*route.Route{
				"1": {
					ID:         "1",
					PeerGroups: make([]string, 1),
				},
			},
			Users: map[string]*types.User{
				"1": {
					Id:            "1",
					IsServiceUser: true,
					PATs: map[string]*types.PersonalAccessToken{
						"1": {},
					},
				},
				idpUserID: {
					Id:            idpUserID,
					IsServiceUser: false,
					PATs: map[string]*types.PersonalAccessToken{
						"1": {},
					},
				},
			},
			Networks: []*networkTypes.Network{
				{
					ID:        "1",
					AccountID: "1",
				},
			},
			NetworkResources: []*resourceTypes.NetworkResource{
				{
					ID:        "1",
					AccountID: "1",
					NetworkID: "1",
				},
				{
					ID:        "2",
					AccountID: "1",
					NetworkID: "1",
				},
			},
			NetworkRouters: []*routerTypes.NetworkRouter{
				{
					ID:        "1",
					AccountID: "1",
					NetworkID: "1",
				},
			},
		},
	}
}

// GetStoreEngine returns FileStoreEngine
func (mockDatasource) GetStoreEngine() types.Engine {
	return types.FileStoreEngine
}

// TestGenerateProperties tests and validate the properties generation by using the mockDatasource for the Worker.generateProperties
func TestGenerateProperties(t *testing.T) {
	ds := mockDatasource{}
	worker := Worker{
		dataSource:  ds,
		connManager: ds,
		idpManager:  EmbeddedType,
	}

	properties := worker.generateProperties(context.Background())

	if properties["accounts"] != 2 {
		t.Errorf("expected 2 accounts, got %d", properties["accounts"])
	}
	if properties["peers"] != 2 {
		t.Errorf("expected 2 peers, got %d", properties["peers"])
	}
	if properties["routes"] != 2 {
		t.Errorf("expected 2 routes, got %d", properties["routes"])
	}
	if properties["networks"] != 1 {
		t.Errorf("expected 1 networks, got %d", properties["networks"])
	}
	if properties["network_resources"] != 2 {
		t.Errorf("expected 2 network_resources, got %d", properties["network_resources"])
	}
	if properties["network_routers"] != 1 {
		t.Errorf("expected 1 network_routers, got %d", properties["network_routers"])
	}
	if properties["rules"] != 4 {
		t.Errorf("expected 4 rules, got %d", properties["rules"])
	}
	if properties["users"] != 2 {
		t.Errorf("expected 1 users, got %d", properties["users"])
	}
	if properties["setup_keys_usage"] != 2 {
		t.Errorf("expected 1 setup_keys_usage, got %d", properties["setup_keys_usage"])
	}
	if properties["pats"] != 4 {
		t.Errorf("expected 4 personal_access_tokens, got %d", properties["pats"])
	}
	if properties["peers_ssh_enabled"] != 2 {
		t.Errorf("expected 2 peers_ssh_enabled, got %d", properties["peers_ssh_enabled"])
	}
	if properties["routes_with_routing_groups"] != 2 {
		t.Errorf("expected 2 routes_with_routing_groups, got %d", properties["routes_with_routing_groups"])
	}
	if properties["rules_protocol_tcp"] != 4 {
		t.Errorf("expected 4 rules_protocol_tcp, got %d", properties["rules_protocol_tcp"])
	}
	if properties["rules_direction_oneway"] != 2 {
		t.Errorf("expected 2 rules_direction_oneway, got %d", properties["rules_direction_oneway"])
	}

	if properties["active_peers_last_day"] != 2 {
		t.Errorf("expected 2 active_peers_last_day, got %d", properties["active_peers_last_day"])
	}
	if properties["min_active_peer_version"] != "0.0.1" {
		t.Errorf("expected 0.0.1 min_active_peer_version, got %s", properties["min_active_peer_version"])
	}
	if properties["max_active_peer_version"] != "0.0.1" {
		t.Errorf("expected 0.0.1 max_active_peer_version, got %s", properties["max_active_peer_version"])
	}

	if properties["peers_login_expiration_enabled"] != 2 {
		t.Errorf("expected 2 peers_login_expiration_enabled, got %d", properties["peers_login_expiration_enabled"])
	}

	if properties["service_users"] != 2 {
		t.Errorf("expected 2 service_users, got %d", properties["service_users"])
	}

	if properties["peer_os_linux"] != 2 {
		t.Errorf("expected 2 peer_os_linux, got %d", properties["peer_os_linux"])
	}

	if properties["ephemeral_peers_setup_keys"] != 2 {
		t.Errorf("expected 2 ephemeral_peers_setup_keys, got %d", properties["ephemeral_peers_setup_keys_usage"])
	}

	if properties["ephemeral_peers_setup_keys_usage"] != 2 {
		t.Errorf("expected 2 ephemeral_peers_setup_keys_usage, got %d", properties["ephemeral_peers_setup_keys_usage"])
	}

	if properties["nameservers"] != 2 {
		t.Errorf("expected 2 nameservers, got %d", properties["nameservers"])
	}

	if properties["groups"] != 4 {
		t.Errorf("expected 4 groups, got %d", properties["groups"])
	}

	if properties["user_peers"] != 2 {
		t.Errorf("expected 2 user_peers, got %d", properties["user_peers"])
	}

	if properties["store_engine"] != types.FileStoreEngine {
		t.Errorf("expected JsonFile, got %s", properties["store_engine"])
	}

	if properties["rules_with_src_posture_checks"] != 1 {
		t.Errorf("expected 1 rules_with_src_posture_checks, got %d", properties["rules_with_src_posture_checks"])
	}

	if properties["posture_checks"] != 2 {
		t.Errorf("expected 2 posture_checks, got %d", properties["posture_checks"])
	}

	if properties["rosenpass_enabled"] != 1 {
		t.Errorf("expected 1 rosenpass_enabled, got %d", properties["rosenpass_enabled"])
	}

	if properties["active_user_peers_last_day"] != 2 {
		t.Errorf("expected 2 active_user_peers_last_day, got %d", properties["active_user_peers_last_day"])
	}

	if properties["active_users_last_day"] != 1 {
		t.Errorf("expected 1 active_users_last_day, got %d", properties["active_users_last_day"])
	}

	if properties["local_users_count"] != 1 {
		t.Errorf("expected 1 local_users_count, got %d", properties["local_users_count"])
	}
	if properties["idp_users_count"] != 1 {
		t.Errorf("expected 1 idp_users_count, got %d", properties["idp_users_count"])
	}
	if properties["embedded_idp_users_zitadel"] != 1 {
		t.Errorf("expected 1 embedded_idp_users_zitadel, got %v", properties["embedded_idp_users_zitadel"])
	}
}

func TestExtractIdpType(t *testing.T) {
	tests := []struct {
		connectorID string
		expected    string
	}{
		{"okta-abc123def", "okta"},
		{"zitadel-d5uv82dra0haedlf6kv0", "zitadel"},
		{"entra-xyz789", "entra"},
		{"google-abc123", "google"},
		{"pocketid-abc123", "pocketid"},
		{"microsoft-abc123", "microsoft"},
		{"authentik-abc123", "authentik"},
		{"keycloak-d5uv82dra0haedlf6kv0", "keycloak"},
		{"local", "oidc"},
		{"", "oidc"},
	}

	for _, tt := range tests {
		t.Run(tt.connectorID, func(t *testing.T) {
			result := extractIdpType(tt.connectorID)
			if result != tt.expected {
				t.Errorf("extractIdpType(%q) = %q, want %q", tt.connectorID, result, tt.expected)
			}
		})
	}
}

package metrics

import (
	"context"
	"testing"

	nbdns "github.com/netbirdio/netbird/dns"
	"github.com/netbirdio/netbird/idp/dex"
	resourceTypes "github.com/netbirdio/netbird/management/server/networks/resources/types"
	routerTypes "github.com/netbirdio/netbird/management/server/networks/routers/types"
	networkTypes "github.com/netbirdio/netbird/management/server/networks/types"
	nbpeer "github.com/netbirdio/netbird/management/server/peer"
	"github.com/netbirdio/netbird/management/server/posture"
	"github.com/netbirdio/netbird/management/server/types"
	"github.com/netbirdio/netbird/route"
)

type mockDatasource struct{}

// GetAllConnectedPeers returns a map of connected peer IDs for use in tests with predefined information
func (mockDatasource) GetAllConnectedPeers() map[string]struct{} {
	return map[string]struct{}{
		"1": {},
	}
}

// GetAllAccounts returns a list of *server.Account for use in tests with predefined information
func (mockDatasource) GetAllAccounts(_ context.Context) []*types.Account {
	localUserID := dex.EncodeDexUserID("10", "local")
	idpUserID := dex.EncodeDexUserID("20", "zitadel-d5uv82dra0haedlf6kv0")
	return []*types.Account{
		{
			Id:       "1",
			Settings: &types.Settings{PeerLoginExpirationEnabled: true},
			SetupKeys: map[string]*types.SetupKey{
				"1": {
					Id:        "1",
					Ephemeral: true,
					UsedTimes: 1,
				},
			},
			Groups: map[string]*types.Group{
				"1": {},
				"2": {},
			},
			NameServerGroups: map[string]*nbdns.NameServerGroup{
				"1": {},
			},
			Peers: map[string]*nbpeer.Peer{
				"1": {
					ID:         "1",
					UserID:     "test",
					SSHEnabled: false,
					Meta:       nbpeer.PeerSystemMeta{GoOS: "linux", WtVersion: "0.0.1", Flags: nbpeer.Flags{ServerSSHAllowed: true, RosenpassEnabled: true}},
				},
			},
			Policies: []*types.Policy{
				{
					Rules: []*types.PolicyRule{
						{
							Bidirectional: true,
							Protocol:      types.PolicyRuleProtocolTCP,
						},
					},
				},
				{
					Rules: []*types.PolicyRule{
						{
							Bidirectional: false,
							Protocol:      types.PolicyRuleProtocolTCP,
						},
					},
					SourcePostureChecks: []string{"1"},
				},
			},
			Routes: map[route.ID]*route.Route{
				"1": {
					ID:         "1",
					PeerGroups: make([]string, 1),
				},
			},
			PostureChecks: []*posture.Checks{
				{
					ID:   "1",
					Name: "test",
					Checks: posture.ChecksDefinition{
						NBVersionCheck: &posture.NBVersionCheck{
							MinVersion: "0.0.1",
						},
					},
				},
				{
					ID:   "2",
					Name: "tes2",
					Checks: posture.ChecksDefinition{
						NBVersionCheck: &posture.NBVersionCheck{
							MinVersion: "0.0.2",
						},
					},
				},
			},
			Users: map[string]*types.User{
				"1": {
					Id:            "1",
					IsServiceUser: true,
					PATs: map[string]*types.PersonalAccessToken{
						"1": {},
					},
				},
				localUserID: {
					Id:            localUserID,
					IsServiceUser: false,
					PATs: map[string]*types.PersonalAccessToken{
						"1": {},
					},
				},
			},
		},
		{
			Id:       "2",
			Settings: &types.Settings{PeerLoginExpirationEnabled: true},
			SetupKeys: map[string]*types.SetupKey{
				"1": {
					Id:        "1",
					Ephemeral: true,
					UsedTimes: 1,
				},
			},
			Groups: map[string]*types.Group{
				"1": {},
				"2": {},
			},
			NameServerGroups: map[string]*nbdns.NameServerGroup{
				"1": {},
			},
			Peers: map[string]*nbpeer.Peer{
				"1": {
					ID:         "1",
					UserID:     "test",
					SSHEnabled: true,
					Meta:       nbpeer.PeerSystemMeta{GoOS: "linux", WtVersion: "0.0.1"},
				},
			},
			Policies: []*types.Policy{
				{
					Rules: []*types.PolicyRule{
						{
							Bidirectional: true,
							Protocol:      types.PolicyRuleProtocolTCP,
						},
					},
				},
				{
					Rules: []*types.PolicyRule{
						{
							Bidirectional: false,
							Protocol:      types.PolicyRuleProtocolTCP,
						},
					},
				},
			},
			Routes: map[route.ID]*route.Route{
				"1": {
					ID:         "1",
					PeerGroups: make([]string, 1),
				},
			},
			Users: map[string]*types.User{
				"1": {
					Id:            "1",
					IsServiceUser: true,
					PATs: map[string]*types.PersonalAccessToken{
						"1": {},
					},
				},
				idpUserID: {
					Id:            idpUserID,
					IsServiceUser: false,
					PATs: map[string]*types.PersonalAccessToken{
						"1": {},
					},
				},
			},
			Networks: []*networkTypes.Network{
				{
					ID:        "1",
					AccountID: "1",
				},
			},
			NetworkResources: []*resourceTypes.NetworkResource{
				{
					ID:        "1",
					AccountID: "1",
					NetworkID: "1",
				},
				{
					ID:        "2",
					AccountID: "1",
					NetworkID: "1",
				},
			},
			NetworkRouters: []*routerTypes.NetworkRouter{
				{
					ID:        "1",
					AccountID: "1",
					NetworkID: "1",
				},
			},
		},
	}
}

// GetStoreEngine returns FileStoreEngine
func (mockDatasource) GetStoreEngine() types.Engine {
	return types.FileStoreEngine
}

// TestGenerateProperties tests and validate the properties generation by using the mockDatasource for the Worker.generateProperties
func TestGenerateProperties(t *testing.T) {
	ds := mockDatasource{}
	worker := Worker{
		dataSource:  ds,
		connManager: ds,
		idpManager:  EmbeddedType,
	}

	properties := worker.generateProperties(context.Background())

	if properties["accounts"] != 2 {
		t.Errorf("expected 2 accounts, got %d", properties["accounts"])
	}
	if properties["peers"] != 2 {
		t.Errorf("expected 2 peers, got %d", properties["peers"])
	}
	if properties["routes"] != 2 {
		t.Errorf("expected 2 routes, got %d", properties["routes"])
	}
	if properties["networks"] != 1 {
		t.Errorf("expected 1 networks, got %d", properties["networks"])
	}
	if properties["network_resources"] != 2 {
		t.Errorf("expected 2 network_resources, got %d", properties["network_resources"])
	}
	if properties["network_routers"] != 1 {
		t.Errorf("expected 1 network_routers, got %d", properties["network_routers"])
	}
	if properties["rules"] != 4 {
		t.Errorf("expected 4 rules, got %d", properties["rules"])
	}
	if properties["users"] != 2 {
		t.Errorf("expected 1 users, got %d", properties["users"])
	}
	if properties["setup_keys_usage"] != 2 {
		t.Errorf("expected 1 setup_keys_usage, got %d", properties["setup_keys_usage"])
	}
	if properties["pats"] != 4 {
		t.Errorf("expected 4 personal_access_tokens, got %d", properties["pats"])
	}
	if properties["peers_ssh_enabled"] != 2 {
		t.Errorf("expected 2 peers_ssh_enabled, got %d", properties["peers_ssh_enabled"])
	}
	if properties["routes_with_routing_groups"] != 2 {
		t.Errorf("expected 2 routes_with_routing_groups, got %d", properties["routes_with_routing_groups"])
	}
	if properties["rules_protocol_tcp"] != 4 {
		t.Errorf("expected 4 rules_protocol_tcp, got %d", properties["rules_protocol_tcp"])
	}
	if properties["rules_direction_oneway"] != 2 {
		t.Errorf("expected 2 rules_direction_oneway, got %d", properties["rules_direction_oneway"])
	}

	if properties["active_peers_last_day"] != 2 {
		t.Errorf("expected 2 active_peers_last_day, got %d", properties["active_peers_last_day"])
	}
	if properties["min_active_peer_version"] != "0.0.1" {
		t.Errorf("expected 0.0.1 min_active_peer_version, got %s", properties["min_active_peer_version"])
	}
	if properties["max_active_peer_version"] != "0.0.1" {
		t.Errorf("expected 0.0.1 max_active_peer_version, got %s", properties["max_active_peer_version"])
	}

	if properties["peers_login_expiration_enabled"] != 2 {
		t.Errorf("expected 2 peers_login_expiration_enabled, got %d", properties["peers_login_expiration_enabled"])
	}

	if properties["service_users"] != 2 {
		t.Errorf("expected 2 service_users, got %d", properties["service_users"])
	}

	if properties["peer_os_linux"] != 2 {
		t.Errorf("expected 2 peer_os_linux, got %d", properties["peer_os_linux"])
	}

	if properties["ephemeral_peers_setup_keys"] != 2 {
		t.Errorf("expected 2 ephemeral_peers_setup_keys, got %d", properties["ephemeral_peers_setup_keys_usage"])
	}

	if properties["ephemeral_peers_setup_keys_usage"] != 2 {
		t.Errorf("expected 2 ephemeral_peers_setup_keys_usage, got %d", properties["ephemeral_peers_setup_keys_usage"])
	}

	if properties["nameservers"] != 2 {
		t.Errorf("expected 2 nameservers, got %d", properties["nameservers"])
	}

	if properties["groups"] != 4 {
		t.Errorf("expected 4 groups, got %d", properties["groups"])
	}

	if properties["user_peers"] != 2 {
		t.Errorf("expected 2 user_peers, got %d", properties["user_peers"])
	}

	if properties["store_engine"] != types.FileStoreEngine {
		t.Errorf("expected JsonFile, got %s", properties["store_engine"])
	}

	if properties["rules_with_src_posture_checks"] != 1 {
		t.Errorf("expected 1 rules_with_src_posture_checks, got %d", properties["rules_with_src_posture_checks"])
	}

	if properties["posture_checks"] != 2 {
		t.Errorf("expected 2 posture_checks, got %d", properties["posture_checks"])
	}

	if properties["rosenpass_enabled"] != 1 {
		t.Errorf("expected 1 rosenpass_enabled, got %d", properties["rosenpass_enabled"])
	}

	if properties["active_user_peers_last_day"] != 2 {
		t.Errorf("expected 2 active_user_peers_last_day, got %d", properties["active_user_peers_last_day"])
	}

	if properties["active_users_last_day"] != 1 {
		t.Errorf("expected 1 active_users_last_day, got %d", properties["active_users_last_day"])
	}

	if properties["local_users_count"] != 1 {
		t.Errorf("expected 1 local_users_count, got %d", properties["local_users_count"])
	}
	if properties["idp_users_count"] != 1 {
		t.Errorf("expected 1 idp_users_count, got %d", properties["idp_users_count"])
	}
	if properties["embedded_idp_users_zitadel"] != 1 {
		t.Errorf("expected 1 embedded_idp_users_zitadel, got %v", properties["embedded_idp_users_zitadel"])
	}
}

func TestExtractIdpType(t *testing.T) {
	tests := []struct {
		connectorID string
		expected    string
	}{
		{"okta-abc123def", "okta"},
		{"zitadel-d5uv82dra0haedlf6kv0", "zitadel"},
		{"entra-xyz789", "entra"},
		{"google-abc123", "google"},
		{"pocketid-abc123", "pocketid"},
		{"microsoft-abc123", "microsoft"},
		{"authentik-abc123", "authentik"},
		{"keycloak-d5uv82dra0haedlf6kv0", "keycloak"},
		{"local", "oidc"},
		{"", "oidc"},
	}

	for _, tt := range tests {
		t.Run(tt.connectorID, func(t *testing.T) {
			result := extractIdpType(tt.connectorID)
			if result != tt.expected {
				t.Errorf("extractIdpType(%q) = %q, want %q", tt.connectorID, result, tt.expected)
			}
		})
	}
}

-  idpUserID := dex.EncodeDexUserID("20", "zitadel")

+  idpUserID := dex.EncodeDexUserID("20", "zitadel-d5uv82dra0haedlf6kv0")


Bash

go test ./management/server/metrics/ -v -run "TestGenerateProperties|TestExtractIdpType" 2>&1

Run the metrics tests again


Run the metrics tests again


Output:
    --- PASS: TestExtractIdpType/local (0.00s)
    --- PASS: TestExtractIdpType/#00 (0.00s)
PASS
ok  	github.com/netbirdio/netbird/management/server/metrics	2.392s

=== RUN   TestGenerateProperties
--- PASS: TestGenerateProperties (0.00s)
=== RUN   TestExtractIdpType
=== RUN   TestExtractIdpType/okta-abc123def
=== RUN   REDACTED
=== RUN   TestExtractIdpType/entra-xyz789
=== RUN   TestExtractIdpType/google-abc123
=== RUN   TestExtractIdpType/pocketid-abc123
=== RUN   TestExtractIdpType/microsoft-abc123
=== RUN   TestExtractIdpType/authentik-abc123
=== RUN   REDACTED
=== RUN   TestExtractIdpType/local
=== RUN   TestExtractIdpType/#00
--- PASS: TestExtractIdpType (0.00s)
    --- PASS: TestExtractIdpType/okta-abc123def (0.00s)
    --- PASS: REDACTED (0.00s)
    --- PASS: TestExtractIdpType/entra-xyz789 (0.00s)
    --- PASS: TestExtractIdpType/google-abc123 (0.00s)
    --- PASS: TestExtractIdpType/pocketid-abc123 (0.00s)
    --- PASS: TestExtractIdpType/microsoft-abc123 (0.00s)
    --- PASS: TestExtractIdpType/authentik-abc123 (0.00s)
    --- PASS: REDACTED (0.00s)
    --- PASS: TestExtractIdpType/local (0.00s)
    --- PASS: TestExtractIdpType/#00 (0.00s)
PASS
ok  	github.com/netbirdio/netbird/management/server/metrics	2.392s


=== RUN   TestGenerateProperties
--- PASS: TestGenerateProperties (0.00s)
=== RUN   TestExtractIdpType
=== RUN   TestExtractIdpType/okta-abc123def
=== RUN   REDACTED
=== RUN   TestExtractIdpType/entra-xyz789
=== RUN   TestExtractIdpType/google-abc123
=== RUN   TestExtractIdpType/pocketid-abc123
=== RUN   TestExtractIdpType/microsoft-abc123
=== RUN   TestExtractIdpType/authentik-abc123
=== RUN   REDACTED
=== RUN   TestExtractIdpType/local
=== RUN   TestExtractIdpType/#00
--- PASS: TestExtractIdpType (0.00s)
    --- PASS: TestExtractIdpType/okta-abc123def (0.00s)
    --- PASS: REDACTED (0.00s)
    --- PASS: TestExtractIdpType/entra-xyz789 (0.00s)
    --- PASS: TestExtractIdpType/google-abc123 (0.00s)
    --- PASS: TestExtractIdpType/pocketid-abc123 (0.00s)
    --- PASS: TestExtractIdpType/microsoft-abc123 (0.00s)
    --- PASS: TestExtractIdpType/authentik-abc123 (0.00s)
    --- PASS: REDACTED (0.00s)
    --- PASS: TestExtractIdpType/local (0.00s)
    --- PASS: TestExtractIdpType/#00 (0.00s)
PASS
ok  	github.com/netbirdio/netbird/management/server/metrics	2.392s


All tests pass. Added:

1. **`embedded_idp_users_zitadel` assertion** in `TestGenerateProperties` — verifies the new metric is emitted correctly (also fixed the mock connector ID to use realistic format `"zitadel-d5uv82dra0haedlf6kv0"`).

2. **`TestExtractIdpType`** — table-driven test covering all IdP types (okta, zitadel, entra, google, pocketid, microsoft, authentik, keycloak) plus edge cases (`"local"` and empty string both return `"oidc"`).