//go:build !consulent
// +build !consulent

package usagemetrics

import (
	"testing"
	"time"

	"github.com/armon/go-metrics"
	"github.com/stretchr/testify/require"

	"github.com/hashicorp/consul/agent/consul/state"
	"github.com/hashicorp/consul/agent/structs"
	"github.com/hashicorp/consul/sdk/testutil"
	"github.com/hashicorp/serf/serf"
)

func newStateStore() (*state.Store, error) {
	return state.NewStateStore(nil), nil
}

func TestUsageReporter_emitNodeUsage_OSS(t *testing.T) {
	type testCase struct {
		modfiyStateStore func(t *testing.T, s *state.Store)
		getMembersFunc   getMembersFunc
		expectedGauges   map[string]metrics.GaugeValue
	}
	cases := map[string]testCase{
		"empty-state": {
			expectedGauges: map[string]metrics.GaugeValue{
				// --- node ---
				"consul.usage.test.consul.state.nodes;datacenter=dc1": {
					Name:   "consul.usage.test.consul.state.nodes",
					Value:  0,
					Labels: []metrics.Label{{Name: "datacenter", Value: "dc1"}},
				},
				// --- member ---
				"consul.usage.test.consul.members.clients;datacenter=dc1": {
					Name:   "consul.usage.test.consul.members.clients",
					Value:  0,
					Labels: []metrics.Label{{Name: "datacenter", Value: "dc1"}},
				},
				"consul.usage.test.consul.members.servers;datacenter=dc1": {
					Name:   "consul.usage.test.consul.members.servers",
					Value:  0,
					Labels: []metrics.Label{{Name: "datacenter", Value: "dc1"}},
				},
				// --- service ---
				"consul.usage.test.consul.state.services;datacenter=dc1": {
					Name:   "consul.usage.test.consul.state.services",
					Value:  0,
					Labels: []metrics.Label{{Name: "datacenter", Value: "dc1"}},
				},
				"consul.usage.test.consul.state.service_instances;datacenter=dc1": {
					Name:   "consul.usage.test.consul.state.service_instances",
					Value:  0,
					Labels: []metrics.Label{{Name: "datacenter", Value: "dc1"}},
				},
				// --- service mesh ---
				"consul.usage.test.consul.state.connect_instances;datacenter=dc1;kind=connect-proxy": {
					Name:  "consul.usage.test.consul.state.connect_instances",
					Value: 0,
					Labels: []metrics.Label{
						{Name: "datacenter", Value: "dc1"},
						{Name: "kind", Value: "connect-proxy"},
					},
				},
				"consul.usage.test.consul.state.connect_instances;datacenter=dc1;kind=terminating-gateway": {
					Name:  "consul.usage.test.consul.state.connect_instances",
					Value: 0,
					Labels: []metrics.Label{
						{Name: "datacenter", Value: "dc1"},
						{Name: "kind", Value: "terminating-gateway"},
					},
				},
				"consul.usage.test.consul.state.connect_instances;datacenter=dc1;kind=ingress-gateway": {
					Name:  "consul.usage.test.consul.state.connect_instances",
					Value: 0,
					Labels: []metrics.Label{
						{Name: "datacenter", Value: "dc1"},
						{Name: "kind", Value: "ingress-gateway"},
					},
				},
				"consul.usage.test.consul.state.connect_instances;datacenter=dc1;kind=mesh-gateway": {
					Name:  "consul.usage.test.consul.state.connect_instances",
					Value: 0,
					Labels: []metrics.Label{
						{Name: "datacenter", Value: "dc1"},
						{Name: "kind", Value: "mesh-gateway"},
					},
				},
				"consul.usage.test.consul.state.connect_instances;datacenter=dc1;kind=connect-native": {
					Name:  "consul.usage.test.consul.state.connect_instances",
					Value: 0,
					Labels: []metrics.Label{
						{Name: "datacenter", Value: "dc1"},
						{Name: "kind", Value: "connect-native"},
					},
				},
				// --- kv ---
				"consul.usage.test.consul.state.kv_entries;datacenter=dc1": {
					Name:   "consul.usage.test.consul.state.kv_entries",
					Value:  0,
					Labels: []metrics.Label{{Name: "datacenter", Value: "dc1"}},
				},
				// --- config entries ---
				"consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=service-intentions": {
					Name:  "consul.usage.test.consul.state.config_entries",
					Value: 0,
					Labels: []metrics.Label{
						{Name: "datacenter", Value: "dc1"},
						{Name: "kind", Value: "service-intentions"},
					},
				},
				"consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=service-resolver": {
					Name:  "consul.usage.test.consul.state.config_entries",
					Value: 0,
					Labels: []metrics.Label{
						{Name: "datacenter", Value: "dc1"},
						{Name: "kind", Value: "service-resolver"},
					},
				},
				"consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=service-router": {
					Name:  "consul.usage.test.consul.state.config_entries",
					Value: 0,
					Labels: []metrics.Label{
						{Name: "datacenter", Value: "dc1"},
						{Name: "kind", Value: "service-router"},
					},
				},
				"consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=service-defaults": {
					Name:  "consul.usage.test.consul.state.config_entries",
					Value: 0,
					Labels: []metrics.Label{
						{Name: "datacenter", Value: "dc1"},
						{Name: "kind", Value: "service-defaults"},
					},
				},
				"consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=ingress-gateway": {
					Name:  "consul.usage.test.consul.state.config_entries",
					Value: 0,
					Labels: []metrics.Label{
						{Name: "datacenter", Value: "dc1"},
						{Name: "kind", Value: "ingress-gateway"},
					},
				},
				"consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=service-splitter": {
					Name:  "consul.usage.test.consul.state.config_entries",
					Value: 0,
					Labels: []metrics.Label{
						{Name: "datacenter", Value: "dc1"},
						{Name: "kind", Value: "service-splitter"},
					},
				},
				"consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=proxy-defaults": {
					Name:  "consul.usage.test.consul.state.config_entries",
					Value: 0,
					Labels: []metrics.Label{
						{Name: "datacenter", Value: "dc1"},
						{Name: "kind", Value: "proxy-defaults"},
					},
				},
				"consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=terminating-gateway": {
					Name:  "consul.usage.test.consul.state.config_entries",
					Value: 0,
					Labels: []metrics.Label{
						{Name: "datacenter", Value: "dc1"},
						{Name: "kind", Value: "terminating-gateway"},
					},
				},
			},
			getMembersFunc: func() []serf.Member { return []serf.Member{} },
		},
		"nodes": {
			modfiyStateStore: func(t *testing.T, s *state.Store) {
				require.NoError(t, s.EnsureNode(1, &structs.Node{Node: "foo", Address: "127.0.0.1"}))
				require.NoError(t, s.EnsureNode(2, &structs.Node{Node: "bar", Address: "127.0.0.2"}))
				require.NoError(t, s.EnsureNode(3, &structs.Node{Node: "baz", Address: "127.0.0.2"}))
			},
			getMembersFunc: func() []serf.Member {
				return []serf.Member{
					{
						Name:   "foo",
						Tags:   map[string]string{"role": "consul"},
						Status: serf.StatusAlive,
					},
					{
						Name:   "bar",
						Tags:   map[string]string{"role": "consul"},
						Status: serf.StatusAlive,
					},
					{
						Name:   "baz",
						Tags:   map[string]string{"role": "node"},
						Status: serf.StatusAlive,
					},
				}
			},
			expectedGauges: map[string]metrics.GaugeValue{
				// --- node ---
				"consul.usage.test.consul.state.nodes;datacenter=dc1": {
					Name:   "consul.usage.test.consul.state.nodes",
					Value:  3,
					Labels: []metrics.Label{{Name: "datacenter", Value: "dc1"}},
				},
				// --- member ---
				"consul.usage.test.consul.members.servers;datacenter=dc1": {
					Name:   "consul.usage.test.consul.members.servers",
					Value:  2,
					Labels: []metrics.Label{{Name: "datacenter", Value: "dc1"}},
				},
				"consul.usage.test.consul.members.clients;datacenter=dc1": {
					Name:   "consul.usage.test.consul.members.clients",
					Value:  1,
					Labels: []metrics.Label{{Name: "datacenter", Value: "dc1"}},
				},
				"consul.usage.test.consul.members.clients;segment=;datacenter=dc1": {
					Name:   "consul.usage.test.consul.members.clients",
					Value:  1,
					Labels: []metrics.Label{{Name: "segment", Value: ""}, {Name: "datacenter", Value: "dc1"}},
				},
				// --- service ---
				"consul.usage.test.consul.state.services;datacenter=dc1": {
					Name:   "consul.usage.test.consul.state.services",
					Value:  0,
					Labels: []metrics.Label{{Name: "datacenter", Value: "dc1"}},
				},
				"consul.usage.test.consul.state.service_instances;datacenter=dc1": {
					Name:   "consul.usage.test.consul.state.service_instances",
					Value:  0,
					Labels: []metrics.Label{{Name: "datacenter", Value: "dc1"}},
				},
				// --- service mesh ---
				"consul.usage.test.consul.state.connect_instances;datacenter=dc1;kind=connect-proxy": {
					Name:  "consul.usage.test.consul.state.connect_instances",
					Value: 0,
					Labels: []metrics.Label{
						{Name: "datacenter", Value: "dc1"},
						{Name: "kind", Value: "connect-proxy"},
					},
				},
				"consul.usage.test.consul.state.connect_instances;datacenter=dc1;kind=terminating-gateway": {
					Name:  "consul.usage.test.consul.state.connect_instances",
					Value: 0,
					Labels: []metrics.Label{
						{Name: "datacenter", Value: "dc1"},
						{Name: "kind", Value: "terminating-gateway"},
					},
				},
				"consul.usage.test.consul.state.connect_instances;datacenter=dc1;kind=ingress-gateway": {
					Name:  "consul.usage.test.consul.state.connect_instances",
					Value: 0,
					Labels: []metrics.Label{
						{Name: "datacenter", Value: "dc1"},
						{Name: "kind", Value: "ingress-gateway"},
					},
				},
				"consul.usage.test.consul.state.connect_instances;datacenter=dc1;kind=mesh-gateway": {
					Name:  "consul.usage.test.consul.state.connect_instances",
					Value: 0,
					Labels: []metrics.Label{
						{Name: "datacenter", Value: "dc1"},
						{Name: "kind", Value: "mesh-gateway"},
					},
				},
				"consul.usage.test.consul.state.connect_instances;datacenter=dc1;kind=connect-native": {
					Name:  "consul.usage.test.consul.state.connect_instances",
					Value: 0,
					Labels: []metrics.Label{
						{Name: "datacenter", Value: "dc1"},
						{Name: "kind", Value: "connect-native"},
					},
				},
				// --- kv ---
				"consul.usage.test.consul.state.kv_entries;datacenter=dc1": {
					Name:   "consul.usage.test.consul.state.kv_entries",
					Value:  0,
					Labels: []metrics.Label{{Name: "datacenter", Value: "dc1"}},
				},
				// --- config entries ---
				"consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=service-intentions": {
					Name:  "consul.usage.test.consul.state.config_entries",
					Value: 0,
					Labels: []metrics.Label{
						{Name: "datacenter", Value: "dc1"},
						{Name: "kind", Value: "service-intentions"},
					},
				},
				"consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=service-resolver": {
					Name:  "consul.usage.test.consul.state.config_entries",
					Value: 0,
					Labels: []metrics.Label{
						{Name: "datacenter", Value: "dc1"},
						{Name: "kind", Value: "service-resolver"},
					},
				},
				"consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=service-router": {
					Name:  "consul.usage.test.consul.state.config_entries",
					Value: 0,
					Labels: []metrics.Label{
						{Name: "datacenter", Value: "dc1"},
						{Name: "kind", Value: "service-router"},
					},
				},
				"consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=service-defaults": {
					Name:  "consul.usage.test.consul.state.config_entries",
					Value: 0,
					Labels: []metrics.Label{
						{Name: "datacenter", Value: "dc1"},
						{Name: "kind", Value: "service-defaults"},
					},
				},
				"consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=ingress-gateway": {
					Name:  "consul.usage.test.consul.state.config_entries",
					Value: 0,
					Labels: []metrics.Label{
						{Name: "datacenter", Value: "dc1"},
						{Name: "kind", Value: "ingress-gateway"},
					},
				},
				"consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=service-splitter": {
					Name:  "consul.usage.test.consul.state.config_entries",
					Value: 0,
					Labels: []metrics.Label{
						{Name: "datacenter", Value: "dc1"},
						{Name: "kind", Value: "service-splitter"},
					},
				},
				"consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=proxy-defaults": {
					Name:  "consul.usage.test.consul.state.config_entries",
					Value: 0,
					Labels: []metrics.Label{
						{Name: "datacenter", Value: "dc1"},
						{Name: "kind", Value: "proxy-defaults"},
					},
				},
				"consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=terminating-gateway": {
					Name:  "consul.usage.test.consul.state.config_entries",
					Value: 0,
					Labels: []metrics.Label{
						{Name: "datacenter", Value: "dc1"},
						{Name: "kind", Value: "terminating-gateway"},
					},
				},
			},
		},
	}

	for name, tcase := range cases {
		t.Run(name, func(t *testing.T) {
			// Only have a single interval for the test
			sink := metrics.NewInmemSink(1*time.Minute, 1*time.Minute)
			cfg := metrics.DefaultConfig("consul.usage.test")
			cfg.EnableHostname = false
			metrics.NewGlobal(cfg, sink)

			mockStateProvider := &mockStateProvider{}
			s, err := newStateStore()
			require.NoError(t, err)
			if tcase.modfiyStateStore != nil {
				tcase.modfiyStateStore(t, s)
			}
			mockStateProvider.On("State").Return(s)

			reporter, err := NewUsageMetricsReporter(
				new(Config).
					WithStateProvider(mockStateProvider).
					WithLogger(testutil.Logger(t)).
					WithDatacenter("dc1").
					WithGetMembersFunc(tcase.getMembersFunc),
			)
			require.NoError(t, err)

			reporter.runOnce()

			intervals := sink.Data()
			require.Len(t, intervals, 1)
			intv := intervals[0]

			assertEqualGaugeMaps(t, tcase.expectedGauges, intv.Gauges)
		})
	}
}

func TestUsageReporter_emitServiceUsage_OSS(t *testing.T) {
	type testCase struct {
		modfiyStateStore func(t *testing.T, s *state.Store)
		getMembersFunc   getMembersFunc
		expectedGauges   map[string]metrics.GaugeValue
	}
	cases := map[string]testCase{
		"empty-state": {
			expectedGauges: map[string]metrics.GaugeValue{
				"consul.usage.test.consul.state.nodes;datacenter=dc1": {
					Name:   "consul.usage.test.consul.state.nodes",
					Value:  0,
					Labels: []metrics.Label{{Name: "datacenter", Value: "dc1"}},
				},
				"consul.usage.test.consul.state.services;datacenter=dc1": {
					Name:  "consul.usage.test.consul.state.services",
					Value: 0,
					Labels: []metrics.Label{
						{Name: "datacenter", Value: "dc1"},
					},
				},
				"consul.usage.test.consul.state.service_instances;datacenter=dc1": {
					Name:  "consul.usage.test.consul.state.service_instances",
					Value: 0,
					Labels: []metrics.Label{
						{Name: "datacenter", Value: "dc1"},
					},
				},
				"consul.usage.test.consul.members.clients;datacenter=dc1": {
					Name:  "consul.usage.test.consul.members.clients",
					Value: 0,
					Labels: []metrics.Label{
						{Name: "datacenter", Value: "dc1"},
					},
				},
				"consul.usage.test.consul.members.servers;datacenter=dc1": {
					Name:  "consul.usage.test.consul.members.servers",
					Value: 0,
					Labels: []metrics.Label{
						{Name: "datacenter", Value: "dc1"},
					},
				},
				// --- service mesh ---
				"consul.usage.test.consul.state.connect_instances;datacenter=dc1;kind=connect-proxy": {
					Name:  "consul.usage.test.consul.state.connect_instances",
					Value: 0,
					Labels: []metrics.Label{
						{Name: "datacenter", Value: "dc1"},
						{Name: "kind", Value: "connect-proxy"},
					},
				},
				"consul.usage.test.consul.state.connect_instances;datacenter=dc1;kind=terminating-gateway": {
					Name:  "consul.usage.test.consul.state.connect_instances",
					Value: 0,
					Labels: []metrics.Label{
						{Name: "datacenter", Value: "dc1"},
						{Name: "kind", Value: "terminating-gateway"},
					},
				},
				"consul.usage.test.consul.state.connect_instances;datacenter=dc1;kind=ingress-gateway": {
					Name:  "consul.usage.test.consul.state.connect_instances",
					Value: 0,
					Labels: []metrics.Label{
						{Name: "datacenter", Value: "dc1"},
						{Name: "kind", Value: "ingress-gateway"},
					},
				},
				"consul.usage.test.consul.state.connect_instances;datacenter=dc1;kind=mesh-gateway": {
					Name:  "consul.usage.test.consul.state.connect_instances",
					Value: 0,
					Labels: []metrics.Label{
						{Name: "datacenter", Value: "dc1"},
						{Name: "kind", Value: "mesh-gateway"},
					},
				},
				"consul.usage.test.consul.state.connect_instances;datacenter=dc1;kind=connect-native": {
					Name:  "consul.usage.test.consul.state.connect_instances",
					Value: 0,
					Labels: []metrics.Label{
						{Name: "datacenter", Value: "dc1"},
						{Name: "kind", Value: "connect-native"},
					},
				},
				// --- kv ---
				"consul.usage.test.consul.state.kv_entries;datacenter=dc1": {
					Name:   "consul.usage.test.consul.state.kv_entries",
					Value:  0,
					Labels: []metrics.Label{{Name: "datacenter", Value: "dc1"}},
				},
				// --- config entries ---
				"consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=service-intentions": {
					Name:  "consul.usage.test.consul.state.config_entries",
					Value: 0,
					Labels: []metrics.Label{
						{Name: "datacenter", Value: "dc1"},
						{Name: "kind", Value: "service-intentions"},
					},
				},
				"consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=service-resolver": {
					Name:  "consul.usage.test.consul.state.config_entries",
					Value: 0,
					Labels: []metrics.Label{
						{Name: "datacenter", Value: "dc1"},
						{Name: "kind", Value: "service-resolver"},
					},
				},
				"consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=service-router": {
					Name:  "consul.usage.test.consul.state.config_entries",
					Value: 0,
					Labels: []metrics.Label{
						{Name: "datacenter", Value: "dc1"},
						{Name: "kind", Value: "service-router"},
					},
				},
				"consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=service-defaults": {
					Name:  "consul.usage.test.consul.state.config_entries",
					Value: 0,
					Labels: []metrics.Label{
						{Name: "datacenter", Value: "dc1"},
						{Name: "kind", Value: "service-defaults"},
					},
				},
				"consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=ingress-gateway": {
					Name:  "consul.usage.test.consul.state.config_entries",
					Value: 0,
					Labels: []metrics.Label{
						{Name: "datacenter", Value: "dc1"},
						{Name: "kind", Value: "ingress-gateway"},
					},
				},
				"consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=service-splitter": {
					Name:  "consul.usage.test.consul.state.config_entries",
					Value: 0,
					Labels: []metrics.Label{
						{Name: "datacenter", Value: "dc1"},
						{Name: "kind", Value: "service-splitter"},
					},
				},
				"consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=proxy-defaults": {
					Name:  "consul.usage.test.consul.state.config_entries",
					Value: 0,
					Labels: []metrics.Label{
						{Name: "datacenter", Value: "dc1"},
						{Name: "kind", Value: "proxy-defaults"},
					},
				},
				"consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=terminating-gateway": {
					Name:  "consul.usage.test.consul.state.config_entries",
					Value: 0,
					Labels: []metrics.Label{
						{Name: "datacenter", Value: "dc1"},
						{Name: "kind", Value: "terminating-gateway"},
					},
				},
			},
			getMembersFunc: func() []serf.Member { return []serf.Member{} },
		},
		"nodes-and-services": {
			modfiyStateStore: func(t *testing.T, s *state.Store) {
				require.Nil(t, s.EnsureNode(1, &structs.Node{Node: "foo", Address: "127.0.0.1"}))
				require.Nil(t, s.EnsureNode(2, &structs.Node{Node: "bar", Address: "127.0.0.2"}))
				require.Nil(t, s.EnsureNode(3, &structs.Node{Node: "baz", Address: "127.0.0.2"}))
				require.Nil(t, s.EnsureNode(4, &structs.Node{Node: "qux", Address: "127.0.0.3"}))

				mgw := structs.TestNodeServiceMeshGateway(t)
				mgw.ID = "mesh-gateway"

				tgw := structs.TestNodeServiceTerminatingGateway(t, "1.1.1.1")
				tgw.ID = "terminating-gateway"

				igw := structs.TestNodeServiceIngressGateway(t, "1.1.1.1")
				igw.ID = "ingress-gateway"
				// Typical services and some consul services spread across two nodes
				require.NoError(t, s.EnsureService(5, "foo", &structs.NodeService{ID: "db", Service: "db", Tags: nil, Address: "", Port: 5000}))
				require.NoError(t, s.EnsureService(6, "bar", &structs.NodeService{ID: "api", Service: "api", Tags: nil, Address: "", Port: 5000}))
				require.NoError(t, s.EnsureService(7, "foo", &structs.NodeService{ID: "consul", Service: "consul", Tags: nil}))
				require.NoError(t, s.EnsureService(8, "bar", &structs.NodeService{ID: "consul", Service: "consul", Tags: nil}))
				require.NoError(t, s.EnsureService(9, "foo", &structs.NodeService{ID: "db-connect-proxy", Service: "db-connect-proxy", Tags: nil, Address: "", Port: 5000, Kind: structs.ServiceKindConnectProxy}))
				require.NoError(t, s.EnsureService(10, "foo", igw))
				require.NoError(t, s.EnsureService(11, "foo", mgw))
				require.NoError(t, s.EnsureService(12, "foo", tgw))
				require.NoError(t, s.EnsureService(13, "bar", &structs.NodeService{ID: "db-native", Service: "db", Tags: nil, Address: "", Port: 5000, Connect: structs.ServiceConnect{Native: true}}))
				require.NoError(t, s.EnsureConfigEntry(14, &structs.IngressGatewayConfigEntry{
					Kind: structs.IngressGateway,
					Name: "foo",
				}, nil))
				require.NoError(t, s.EnsureConfigEntry(15, &structs.IngressGatewayConfigEntry{
					Kind: structs.IngressGateway,
					Name: "bar",
				}, nil))
				require.NoError(t, s.EnsureConfigEntry(16, &structs.IngressGatewayConfigEntry{
					Kind: structs.IngressGateway,
					Name: "baz",
				}, nil))
			},
			getMembersFunc: func() []serf.Member {
				return []serf.Member{
					{
						Name:   "foo",
						Tags:   map[string]string{"role": "consul"},
						Status: serf.StatusAlive,
					},
					{
						Name:   "bar",
						Tags:   map[string]string{"role": "consul"},
						Status: serf.StatusAlive,
					},
					{
						Name:   "baz",
						Tags:   map[string]string{"role": "node", "segment": "a"},
						Status: serf.StatusAlive,
					},
					{
						Name:   "qux",
						Tags:   map[string]string{"role": "node", "segment": "b"},
						Status: serf.StatusAlive,
					},
				}
			},
			expectedGauges: map[string]metrics.GaugeValue{
				"consul.usage.test.consul.state.nodes;datacenter=dc1": {
					Name:   "consul.usage.test.consul.state.nodes",
					Value:  4,
					Labels: []metrics.Label{{Name: "datacenter", Value: "dc1"}},
				},
				// --- service ---
				"consul.usage.test.consul.state.services;datacenter=dc1": {
					Name:  "consul.usage.test.consul.state.services",
					Value: 7,
					Labels: []metrics.Label{
						{Name: "datacenter", Value: "dc1"},
					},
				},
				"consul.usage.test.consul.state.service_instances;datacenter=dc1": {
					Name:  "consul.usage.test.consul.state.service_instances",
					Value: 9,
					Labels: []metrics.Label{
						{Name: "datacenter", Value: "dc1"},
					},
				},
				"consul.usage.test.consul.members.clients;datacenter=dc1": {
					Name:  "consul.usage.test.consul.members.clients",
					Value: 2,
					Labels: []metrics.Label{
						{Name: "datacenter", Value: "dc1"},
					},
				},
				"consul.usage.test.consul.members.servers;datacenter=dc1": {
					Name:  "consul.usage.test.consul.members.servers",
					Value: 2,
					Labels: []metrics.Label{
						{Name: "datacenter", Value: "dc1"},
					},
				},
				"consul.usage.test.consul.members.clients;segment=a;datacenter=dc1": {
					Name:  "consul.usage.test.consul.members.clients",
					Value: 1,
					Labels: []metrics.Label{
						{Name: "segment", Value: "a"},
						{Name: "datacenter", Value: "dc1"},
					},
				},
				"consul.usage.test.consul.members.clients;segment=b;datacenter=dc1": {
					Name:  "consul.usage.test.consul.members.clients",
					Value: 1,
					Labels: []metrics.Label{
						{Name: "segment", Value: "b"},
						{Name: "datacenter", Value: "dc1"},
					},
				},
				// --- service mesh ---
				"consul.usage.test.consul.state.connect_instances;datacenter=dc1;kind=connect-proxy": {
					Name:  "consul.usage.test.consul.state.connect_instances",
					Value: 1,
					Labels: []metrics.Label{
						{Name: "datacenter", Value: "dc1"},
						{Name: "kind", Value: "connect-proxy"},
					},
				},
				"consul.usage.test.consul.state.connect_instances;datacenter=dc1;kind=terminating-gateway": {
					Name:  "consul.usage.test.consul.state.connect_instances",
					Value: 1,
					Labels: []metrics.Label{
						{Name: "datacenter", Value: "dc1"},
						{Name: "kind", Value: "terminating-gateway"},
					},
				},
				"consul.usage.test.consul.state.connect_instances;datacenter=dc1;kind=ingress-gateway": {
					Name:  "consul.usage.test.consul.state.connect_instances",
					Value: 1,
					Labels: []metrics.Label{
						{Name: "datacenter", Value: "dc1"},
						{Name: "kind", Value: "ingress-gateway"},
					},
				},
				"consul.usage.test.consul.state.connect_instances;datacenter=dc1;kind=mesh-gateway": {
					Name:  "consul.usage.test.consul.state.connect_instances",
					Value: 1,
					Labels: []metrics.Label{
						{Name: "datacenter", Value: "dc1"},
						{Name: "kind", Value: "mesh-gateway"},
					},
				},
				"consul.usage.test.consul.state.connect_instances;datacenter=dc1;kind=connect-native": {
					Name:  "consul.usage.test.consul.state.connect_instances",
					Value: 1,
					Labels: []metrics.Label{
						{Name: "datacenter", Value: "dc1"},
						{Name: "kind", Value: "connect-native"},
					},
				},
				// --- kv ---
				"consul.usage.test.consul.state.kv_entries;datacenter=dc1": {
					Name:   "consul.usage.test.consul.state.kv_entries",
					Value:  0,
					Labels: []metrics.Label{{Name: "datacenter", Value: "dc1"}},
				},
				// --- config entries ---
				"consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=service-intentions": {
					Name:  "consul.usage.test.consul.state.config_entries",
					Value: 0,
					Labels: []metrics.Label{
						{Name: "datacenter", Value: "dc1"},
						{Name: "kind", Value: "service-intentions"},
					},
				},
				"consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=service-resolver": {
					Name:  "consul.usage.test.consul.state.config_entries",
					Value: 0,
					Labels: []metrics.Label{
						{Name: "datacenter", Value: "dc1"},
						{Name: "kind", Value: "service-resolver"},
					},
				},
				"consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=service-router": {
					Name:  "consul.usage.test.consul.state.config_entries",
					Value: 0,
					Labels: []metrics.Label{
						{Name: "datacenter", Value: "dc1"},
						{Name: "kind", Value: "service-router"},
					},
				},
				"consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=service-defaults": {
					Name:  "consul.usage.test.consul.state.config_entries",
					Value: 0,
					Labels: []metrics.Label{
						{Name: "datacenter", Value: "dc1"},
						{Name: "kind", Value: "service-defaults"},
					},
				},
				"consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=ingress-gateway": {
					Name:  "consul.usage.test.consul.state.config_entries",
					Value: 3,
					Labels: []metrics.Label{
						{Name: "datacenter", Value: "dc1"},
						{Name: "kind", Value: "ingress-gateway"},
					},
				},
				"consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=service-splitter": {
					Name:  "consul.usage.test.consul.state.config_entries",
					Value: 0,
					Labels: []metrics.Label{
						{Name: "datacenter", Value: "dc1"},
						{Name: "kind", Value: "service-splitter"},
					},
				},
				"consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=proxy-defaults": {
					Name:  "consul.usage.test.consul.state.config_entries",
					Value: 0,
					Labels: []metrics.Label{
						{Name: "datacenter", Value: "dc1"},
						{Name: "kind", Value: "proxy-defaults"},
					},
				},
				"consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=terminating-gateway": {
					Name:  "consul.usage.test.consul.state.config_entries",
					Value: 0,
					Labels: []metrics.Label{
						{Name: "datacenter", Value: "dc1"},
						{Name: "kind", Value: "terminating-gateway"},
					},
				},
			},
		},
	}

	for name, tcase := range cases {
		t.Run(name, func(t *testing.T) {
			// Only have a single interval for the test
			sink := metrics.NewInmemSink(1*time.Minute, 1*time.Minute)
			cfg := metrics.DefaultConfig("consul.usage.test")
			cfg.EnableHostname = false
			metrics.NewGlobal(cfg, sink)

			mockStateProvider := &mockStateProvider{}
			s := state.NewStateStore(nil)
			if tcase.modfiyStateStore != nil {
				tcase.modfiyStateStore(t, s)
			}
			mockStateProvider.On("State").Return(s)

			reporter, err := NewUsageMetricsReporter(
				new(Config).
					WithStateProvider(mockStateProvider).
					WithLogger(testutil.Logger(t)).
					WithDatacenter("dc1").
					WithGetMembersFunc(tcase.getMembersFunc),
			)
			require.NoError(t, err)

			reporter.runOnce()

			intervals := sink.Data()
			require.Len(t, intervals, 1)
			intv := intervals[0]

			require.Equal(t, tcase.expectedGauges, intv.Gauges)
		})
	}
}

func TestUsageReporter_emitKVUsage_OSS(t *testing.T) {
	type testCase struct {
		modfiyStateStore func(t *testing.T, s *state.Store)
		getMembersFunc   getMembersFunc
		expectedGauges   map[string]metrics.GaugeValue
	}
	cases := map[string]testCase{
		"empty-state": {
			expectedGauges: map[string]metrics.GaugeValue{
				// --- node ---
				"consul.usage.test.consul.state.nodes;datacenter=dc1": {
					Name:   "consul.usage.test.consul.state.nodes",
					Value:  0,
					Labels: []metrics.Label{{Name: "datacenter", Value: "dc1"}},
				},
				// --- member ---
				"consul.usage.test.consul.members.clients;datacenter=dc1": {
					Name:   "consul.usage.test.consul.members.clients",
					Value:  0,
					Labels: []metrics.Label{{Name: "datacenter", Value: "dc1"}},
				},
				"consul.usage.test.consul.members.servers;datacenter=dc1": {
					Name:   "consul.usage.test.consul.members.servers",
					Value:  0,
					Labels: []metrics.Label{{Name: "datacenter", Value: "dc1"}},
				},
				// --- service ---
				"consul.usage.test.consul.state.services;datacenter=dc1": {
					Name:   "consul.usage.test.consul.state.services",
					Value:  0,
					Labels: []metrics.Label{{Name: "datacenter", Value: "dc1"}},
				},
				"consul.usage.test.consul.state.service_instances;datacenter=dc1": {
					Name:   "consul.usage.test.consul.state.service_instances",
					Value:  0,
					Labels: []metrics.Label{{Name: "datacenter", Value: "dc1"}},
				},
				// --- service mesh ---
				"consul.usage.test.consul.state.connect_instances;datacenter=dc1;kind=connect-proxy": {
					Name:  "consul.usage.test.consul.state.connect_instances",
					Value: 0,
					Labels: []metrics.Label{
						{Name: "datacenter", Value: "dc1"},
						{Name: "kind", Value: "connect-proxy"},
					},
				},
				"consul.usage.test.consul.state.connect_instances;datacenter=dc1;kind=terminating-gateway": {
					Name:  "consul.usage.test.consul.state.connect_instances",
					Value: 0,
					Labels: []metrics.Label{
						{Name: "datacenter", Value: "dc1"},
						{Name: "kind", Value: "terminating-gateway"},
					},
				},
				"consul.usage.test.consul.state.connect_instances;datacenter=dc1;kind=ingress-gateway": {
					Name:  "consul.usage.test.consul.state.connect_instances",
					Value: 0,
					Labels: []metrics.Label{
						{Name: "datacenter", Value: "dc1"},
						{Name: "kind", Value: "ingress-gateway"},
					},
				},
				"consul.usage.test.consul.state.connect_instances;datacenter=dc1;kind=mesh-gateway": {
					Name:  "consul.usage.test.consul.state.connect_instances",
					Value: 0,
					Labels: []metrics.Label{
						{Name: "datacenter", Value: "dc1"},
						{Name: "kind", Value: "mesh-gateway"},
					},
				},
				"consul.usage.test.consul.state.connect_instances;datacenter=dc1;kind=connect-native": {
					Name:  "consul.usage.test.consul.state.connect_instances",
					Value: 0,
					Labels: []metrics.Label{
						{Name: "datacenter", Value: "dc1"},
						{Name: "kind", Value: "connect-native"},
					},
				},
				// --- kv ---
				"consul.usage.test.consul.state.kv_entries;datacenter=dc1": {
					Name:   "consul.usage.test.consul.state.kv_entries",
					Value:  0,
					Labels: []metrics.Label{{Name: "datacenter", Value: "dc1"}},
				},
				// --- config entries ---
				"consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=service-intentions": {
					Name:  "consul.usage.test.consul.state.config_entries",
					Value: 0,
					Labels: []metrics.Label{
						{Name: "datacenter", Value: "dc1"},
						{Name: "kind", Value: "service-intentions"},
					},
				},
				"consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=service-resolver": {
					Name:  "consul.usage.test.consul.state.config_entries",
					Value: 0,
					Labels: []metrics.Label{
						{Name: "datacenter", Value: "dc1"},
						{Name: "kind", Value: "service-resolver"},
					},
				},
				"consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=service-router": {
					Name:  "consul.usage.test.consul.state.config_entries",
					Value: 0,
					Labels: []metrics.Label{
						{Name: "datacenter", Value: "dc1"},
						{Name: "kind", Value: "service-router"},
					},
				},
				"consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=service-defaults": {
					Name:  "consul.usage.test.consul.state.config_entries",
					Value: 0,
					Labels: []metrics.Label{
						{Name: "datacenter", Value: "dc1"},
						{Name: "kind", Value: "service-defaults"},
					},
				},
				"consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=ingress-gateway": {
					Name:  "consul.usage.test.consul.state.config_entries",
					Value: 0,
					Labels: []metrics.Label{
						{Name: "datacenter", Value: "dc1"},
						{Name: "kind", Value: "ingress-gateway"},
					},
				},
				"consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=service-splitter": {
					Name:  "consul.usage.test.consul.state.config_entries",
					Value: 0,
					Labels: []metrics.Label{
						{Name: "datacenter", Value: "dc1"},
						{Name: "kind", Value: "service-splitter"},
					},
				},
				"consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=proxy-defaults": {
					Name:  "consul.usage.test.consul.state.config_entries",
					Value: 0,
					Labels: []metrics.Label{
						{Name: "datacenter", Value: "dc1"},
						{Name: "kind", Value: "proxy-defaults"},
					},
				},
				"consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=terminating-gateway": {
					Name:  "consul.usage.test.consul.state.config_entries",
					Value: 0,
					Labels: []metrics.Label{
						{Name: "datacenter", Value: "dc1"},
						{Name: "kind", Value: "terminating-gateway"},
					},
				},
			},
			getMembersFunc: func() []serf.Member { return []serf.Member{} },
		},
		"nodes": {
			modfiyStateStore: func(t *testing.T, s *state.Store) {
				require.NoError(t, s.EnsureNode(1, &structs.Node{Node: "foo", Address: "127.0.0.1"}))
				require.NoError(t, s.EnsureNode(2, &structs.Node{Node: "bar", Address: "127.0.0.2"}))
				require.NoError(t, s.EnsureNode(3, &structs.Node{Node: "baz", Address: "127.0.0.2"}))

				require.NoError(t, s.KVSSet(4, &structs.DirEntry{Key: "a", Value: []byte{1}}))
				require.NoError(t, s.KVSSet(5, &structs.DirEntry{Key: "b", Value: []byte{1}}))
				require.NoError(t, s.KVSSet(6, &structs.DirEntry{Key: "c", Value: []byte{1}}))
				require.NoError(t, s.KVSSet(7, &structs.DirEntry{Key: "d", Value: []byte{1}}))
				require.NoError(t, s.KVSDelete(8, "d", &structs.EnterpriseMeta{}))
				require.NoError(t, s.KVSDelete(9, "c", &structs.EnterpriseMeta{}))
				require.NoError(t, s.KVSSet(10, &structs.DirEntry{Key: "e", Value: []byte{1}}))
				require.NoError(t, s.KVSSet(11, &structs.DirEntry{Key: "f", Value: []byte{1}}))
			},
			getMembersFunc: func() []serf.Member {
				return []serf.Member{
					{
						Name:   "foo",
						Tags:   map[string]string{"role": "consul"},
						Status: serf.StatusAlive,
					},
					{
						Name:   "bar",
						Tags:   map[string]string{"role": "consul"},
						Status: serf.StatusAlive,
					},
					{
						Name:   "baz",
						Tags:   map[string]string{"role": "node"},
						Status: serf.StatusAlive,
					},
				}
			},
			expectedGauges: map[string]metrics.GaugeValue{
				// --- node ---
				"consul.usage.test.consul.state.nodes;datacenter=dc1": {
					Name:   "consul.usage.test.consul.state.nodes",
					Value:  3,
					Labels: []metrics.Label{{Name: "datacenter", Value: "dc1"}},
				},
				// --- member ---
				"consul.usage.test.consul.members.servers;datacenter=dc1": {
					Name:   "consul.usage.test.consul.members.servers",
					Value:  2,
					Labels: []metrics.Label{{Name: "datacenter", Value: "dc1"}},
				},
				"consul.usage.test.consul.members.clients;datacenter=dc1": {
					Name:   "consul.usage.test.consul.members.clients",
					Value:  1,
					Labels: []metrics.Label{{Name: "datacenter", Value: "dc1"}},
				},
				"consul.usage.test.consul.members.clients;segment=;datacenter=dc1": {
					Name:   "consul.usage.test.consul.members.clients",
					Value:  1,
					Labels: []metrics.Label{{Name: "segment", Value: ""}, {Name: "datacenter", Value: "dc1"}},
				},
				// --- service ---
				"consul.usage.test.consul.state.services;datacenter=dc1": {
					Name:   "consul.usage.test.consul.state.services",
					Value:  0,
					Labels: []metrics.Label{{Name: "datacenter", Value: "dc1"}},
				},
				"consul.usage.test.consul.state.service_instances;datacenter=dc1": {
					Name:   "consul.usage.test.consul.state.service_instances",
					Value:  0,
					Labels: []metrics.Label{{Name: "datacenter", Value: "dc1"}},
				},
				// --- service mesh ---
				"consul.usage.test.consul.state.connect_instances;datacenter=dc1;kind=connect-proxy": {
					Name:  "consul.usage.test.consul.state.connect_instances",
					Value: 0,
					Labels: []metrics.Label{
						{Name: "datacenter", Value: "dc1"},
						{Name: "kind", Value: "connect-proxy"},
					},
				},
				"consul.usage.test.consul.state.connect_instances;datacenter=dc1;kind=terminating-gateway": {
					Name:  "consul.usage.test.consul.state.connect_instances",
					Value: 0,
					Labels: []metrics.Label{
						{Name: "datacenter", Value: "dc1"},
						{Name: "kind", Value: "terminating-gateway"},
					},
				},
				"consul.usage.test.consul.state.connect_instances;datacenter=dc1;kind=ingress-gateway": {
					Name:  "consul.usage.test.consul.state.connect_instances",
					Value: 0,
					Labels: []metrics.Label{
						{Name: "datacenter", Value: "dc1"},
						{Name: "kind", Value: "ingress-gateway"},
					},
				},
				"consul.usage.test.consul.state.connect_instances;datacenter=dc1;kind=mesh-gateway": {
					Name:  "consul.usage.test.consul.state.connect_instances",
					Value: 0,
					Labels: []metrics.Label{
						{Name: "datacenter", Value: "dc1"},
						{Name: "kind", Value: "mesh-gateway"},
					},
				},
				"consul.usage.test.consul.state.connect_instances;datacenter=dc1;kind=connect-native": {
					Name:  "consul.usage.test.consul.state.connect_instances",
					Value: 0,
					Labels: []metrics.Label{
						{Name: "datacenter", Value: "dc1"},
						{Name: "kind", Value: "connect-native"},
					},
				},
				// --- kv ---
				"consul.usage.test.consul.state.kv_entries;datacenter=dc1": {
					Name:   "consul.usage.test.consul.state.kv_entries",
					Value:  4,
					Labels: []metrics.Label{{Name: "datacenter", Value: "dc1"}},
				},
				// --- config entries ---
				"consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=service-intentions": {
					Name:  "consul.usage.test.consul.state.config_entries",
					Value: 0,
					Labels: []metrics.Label{
						{Name: "datacenter", Value: "dc1"},
						{Name: "kind", Value: "service-intentions"},
					},
				},
				"consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=service-resolver": {
					Name:  "consul.usage.test.consul.state.config_entries",
					Value: 0,
					Labels: []metrics.Label{
						{Name: "datacenter", Value: "dc1"},
						{Name: "kind", Value: "service-resolver"},
					},
				},
				"consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=service-router": {
					Name:  "consul.usage.test.consul.state.config_entries",
					Value: 0,
					Labels: []metrics.Label{
						{Name: "datacenter", Value: "dc1"},
						{Name: "kind", Value: "service-router"},
					},
				},
				"consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=service-defaults": {
					Name:  "consul.usage.test.consul.state.config_entries",
					Value: 0,
					Labels: []metrics.Label{
						{Name: "datacenter", Value: "dc1"},
						{Name: "kind", Value: "service-defaults"},
					},
				},
				"consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=ingress-gateway": {
					Name:  "consul.usage.test.consul.state.config_entries",
					Value: 0,
					Labels: []metrics.Label{
						{Name: "datacenter", Value: "dc1"},
						{Name: "kind", Value: "ingress-gateway"},
					},
				},
				"consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=service-splitter": {
					Name:  "consul.usage.test.consul.state.config_entries",
					Value: 0,
					Labels: []metrics.Label{
						{Name: "datacenter", Value: "dc1"},
						{Name: "kind", Value: "service-splitter"},
					},
				},
				"consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=proxy-defaults": {
					Name:  "consul.usage.test.consul.state.config_entries",
					Value: 0,
					Labels: []metrics.Label{
						{Name: "datacenter", Value: "dc1"},
						{Name: "kind", Value: "proxy-defaults"},
					},
				},
				"consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=terminating-gateway": {
					Name:  "consul.usage.test.consul.state.config_entries",
					Value: 0,
					Labels: []metrics.Label{
						{Name: "datacenter", Value: "dc1"},
						{Name: "kind", Value: "terminating-gateway"},
					},
				},
			},
		},
	}

	for name, tcase := range cases {
		t.Run(name, func(t *testing.T) {
			// Only have a single interval for the test
			sink := metrics.NewInmemSink(1*time.Minute, 1*time.Minute)
			cfg := metrics.DefaultConfig("consul.usage.test")
			cfg.EnableHostname = false
			metrics.NewGlobal(cfg, sink)

			mockStateProvider := &mockStateProvider{}
			s, err := newStateStore()
			require.NoError(t, err)
			if tcase.modfiyStateStore != nil {
				tcase.modfiyStateStore(t, s)
			}
			mockStateProvider.On("State").Return(s)

			reporter, err := NewUsageMetricsReporter(
				new(Config).
					WithStateProvider(mockStateProvider).
					WithLogger(testutil.Logger(t)).
					WithDatacenter("dc1").
					WithGetMembersFunc(tcase.getMembersFunc),
			)
			require.NoError(t, err)

			reporter.runOnce()

			intervals := sink.Data()
			require.Len(t, intervals, 1)
			intv := intervals[0]

			assertEqualGaugeMaps(t, tcase.expectedGauges, intv.Gauges)
		})
	}
}
