// Copyright (c) 2017 Arista Networks, Inc.
// Use of this source code is governed by the Apache License 2.0
// that can be found in the COPYING file.

package main

import (
	"regexp"
	"testing"

	"github.com/aristanetworks/goarista/test"

	"github.com/prometheus/client_golang/prometheus"
)

func TestParseConfig(t *testing.T) {
	tCases := []struct {
		input  []byte
		config Config
	}{
		{
			input: []byte(`
devicelabels:
        10.1.1.1:
                lab1: val1
                lab2: val2
        '*':
                lab1: val3
                lab2: val4
subscriptions:
        - /Sysdb/environment/cooling/status
        - /Sysdb/environment/power/status
description-label-subscriptions:
        - /interfaces/interface/state/description
        - /network-instances/network-instance/protocols/protocol/state/description
        - /interfaces/interface/invalid
metrics:
        - name: fanName
          path: /Sysdb/environment/cooling/status/fan/name
          help: Fan Name
          valuelabel: name
          defaultvalue: 25
        - name: intfCounter
          path: /Sysdb/(lag|slice/phy/.+)/intfCounterDir/(?P<intf>.+)/intfCounter
          help: Per-Interface Bytes/Errors/Discards Counters
        - name: fanSpeed
          path: /Sysdb/environment/cooling/fan/speed/value
          help: Fan Speed`),
			config: Config{
				DeviceLabels: map[string]prometheus.Labels{
					"10.1.1.1": {
						"lab1": "val1",
						"lab2": "val2",
					},
					"*": {
						"lab1": "val3",
						"lab2": "val4",
					},
				},
				Subscriptions: []string{
					"/Sysdb/environment/cooling/status",
					"/Sysdb/environment/power/status",
				},
				DescriptionLabelSubscriptions: []string{"/interfaces/interface/state/description",
					"/network-instances/network-instance/protocols/protocol/state/description"},
				Metrics: []*MetricDef{
					{
						Path: "/Sysdb/environment/cooling/status/fan/name",
						re: regexp.MustCompile(
							"/Sysdb/environment/cooling/status/fan/name"),
						Name:         "fanName",
						Help:         "Fan Name",
						ValueLabel:   "name",
						DefaultValue: 25,
						stringMetric: true,
						devDesc: map[string]*promDesc{
							"10.1.1.1": {
								fqName:    "fanName",
								help:      "Fan Name",
								varLabels: []string{"name"},
								devPermLabels: map[string]string{
									"lab1": "val1",
									"lab2": "val2",
								},
							},
						},
						desc: &promDesc{
							fqName:    "fanName",
							help:      "Fan Name",
							varLabels: []string{"name"},
							devPermLabels: map[string]string{
								"lab1": "val3",
								"lab2": "val4",
							},
						},
					},
					{
						Path: "/Sysdb/(lag|slice/phy/.+)/intfCounterDir/(?P<intf>.+)/intfCounter",
						re: regexp.MustCompile(
							"/Sysdb/(lag|slice/phy/.+)/intfCounterDir/(?P<intf>.+)/intfCounter"),
						Name: "intfCounter",
						Help: "Per-Interface Bytes/Errors/Discards Counters",
						devDesc: map[string]*promDesc{
							"10.1.1.1": {
								fqName:        "intfCounter",
								help:          "Per-Interface Bytes/Errors/Discards Counters",
								varLabels:     []string{"unnamedLabel1", "intf"},
								devPermLabels: map[string]string{"lab1": "val1", "lab2": "val2"},
							},
						},
						desc: &promDesc{
							fqName:        "intfCounter",
							help:          "Per-Interface Bytes/Errors/Discards Counters",
							varLabels:     []string{"unnamedLabel1", "intf"},
							devPermLabels: map[string]string{"lab1": "val3", "lab2": "val4"},
						},
					},
					{
						Path: "/Sysdb/environment/cooling/fan/speed/value",
						re:   regexp.MustCompile("/Sysdb/environment/cooling/fan/speed/value"),
						Name: "fanSpeed",
						Help: "Fan Speed",
						devDesc: map[string]*promDesc{
							"10.1.1.1": {
								fqName:        "fanSpeed",
								help:          "Fan Speed",
								varLabels:     []string{},
								devPermLabels: map[string]string{"lab1": "val1", "lab2": "val2"},
							},
						},
						desc: &promDesc{
							fqName:        "fanSpeed",
							help:          "Fan Speed",
							varLabels:     []string{},
							devPermLabels: map[string]string{"lab1": "val3", "lab2": "val4"},
						},
					},
				},
				subsByOrigin: map[string][]string{
					"": []string{
						"/Sysdb/environment/cooling/status",
						"/Sysdb/environment/power/status",
					},
				},
			},
		},
		{
			input: []byte(`
devicelabels:
        '*':
                lab1: val3
                lab2: val4
subscriptions:
        - /Sysdb/environment/cooling/status
        - /Sysdb/environment/power/status
metrics:
        - name: intfCounter
          path: /Sysdb/(?:lag|slice/phy/.+)/intfCounterDir/(?P<intf>.+)/intfCounter
          help: Per-Interface Bytes/Errors/Discards Counters
        - name: fanSpeed
          path: /Sysdb/environment/cooling/fan/speed/value
          help: Fan Speed`),
			config: Config{
				DeviceLabels: map[string]prometheus.Labels{
					"*": {
						"lab1": "val3",
						"lab2": "val4",
					},
				},
				Subscriptions: []string{
					"/Sysdb/environment/cooling/status",
					"/Sysdb/environment/power/status",
				},
				Metrics: []*MetricDef{
					{
						Path: "/Sysdb/(?:lag|slice/phy/.+)/intfCounterDir/(?P<intf>.+)/intfCounter",
						re: regexp.MustCompile(
							"/Sysdb/(?:lag|slice/phy/.+)/intfCounterDir/(?P<intf>.+)/intfCounter"),
						Name:    "intfCounter",
						Help:    "Per-Interface Bytes/Errors/Discards Counters",
						devDesc: map[string]*promDesc{},
						desc: &promDesc{
							fqName:    "intfCounter",
							help:      "Per-Interface Bytes/Errors/Discards Counters",
							varLabels: []string{"intf"},
							devPermLabels: map[string]string{
								"lab1": "val3",
								"lab2": "val4",
							},
						},
					},
					{
						Path: "/Sysdb/environment/cooling/fan/speed/value",
						re: regexp.MustCompile(
							"/Sysdb/environment/cooling/fan/speed/value"),
						Name:    "fanSpeed",
						Help:    "Fan Speed",
						devDesc: map[string]*promDesc{},
						desc: &promDesc{
							fqName: "fanSpeed",
							help:   "Fan Speed",
							devPermLabels: map[string]string{
								"lab1": "val3",
								"lab2": "val4",
							},
						},
					},
				},
				subsByOrigin: map[string][]string{
					"": []string{
						"/Sysdb/environment/cooling/status",
						"/Sysdb/environment/power/status",
					},
				},
			},
		},
		{
			input: []byte(`
devicelabels:
        10.1.1.1:
                lab1: val1
                lab2: val2
subscriptions:
        - eos_native:/Sysdb/environment/cooling/status
        - /Sysdb/environment/power/status
metrics:
        - name: intfCounter
          path: /Sysdb/(?:lag|slice/phy/.+)/intfCounterDir/(?P<intf>.+)/intfCounter
          help: Per-Interface Bytes/Errors/Discards Counters
        - name: fanSpeed
          path: /Sysdb/environment/cooling/fan/speed/value
          help: Fan Speed`),
			config: Config{
				DeviceLabels: map[string]prometheus.Labels{
					"10.1.1.1": {
						"lab1": "val1",
						"lab2": "val2",
					},
				},
				Subscriptions: []string{
					"eos_native:/Sysdb/environment/cooling/status",
					"/Sysdb/environment/power/status",
				},
				Metrics: []*MetricDef{
					{
						Path: "/Sysdb/(?:lag|slice/phy/.+)/intfCounterDir/(?P<intf>.+)/intfCounter",
						re: regexp.MustCompile(
							"/Sysdb/(?:lag|slice/phy/.+)/intfCounterDir/(?P<intf>.+)/intfCounter"),
						Name: "intfCounter",
						Help: "Per-Interface Bytes/Errors/Discards Counters",
						devDesc: map[string]*promDesc{
							"10.1.1.1": {
								fqName:    "intfCounter",
								help:      "Per-Interface Bytes/Errors/Discards Counters",
								varLabels: []string{"intf"},
								devPermLabels: map[string]string{
									"lab1": "val1",
									"lab2": "val2",
								},
							},
						},
					},
					{
						Path: "/Sysdb/environment/cooling/fan/speed/value",
						re: regexp.MustCompile(
							"/Sysdb/environment/cooling/fan/speed/value"),
						Name: "fanSpeed",
						Help: "Fan Speed",
						devDesc: map[string]*promDesc{
							"10.1.1.1": {
								fqName: "fanSpeed",
								help:   "Fan Speed",
								devPermLabels: map[string]string{
									"lab1": "val1",
									"lab2": "val2",
								},
							},
						},
					},
				},
				subsByOrigin: map[string][]string{
					"": []string{
						"/Sysdb/environment/power/status",
					},
					"eos_native": []string{
						"/Sysdb/environment/cooling/status",
					},
				},
			},
		},
		{
			input: []byte(`
subscriptions:
        - /Sysdb/environment/cooling/status
        - /Sysdb/environment/power/status
metrics:
        - name: intfCounter
          path: /Sysdb/(?:lag|slice/phy/.+)/intfCounterDir/(?P<intf>.+)/intfCounter
          help: Per-Interface Bytes/Errors/Discards Counters
        - name: fanSpeed
          path: /Sysdb/environment/cooling/fan/speed/value
          help: Fan Speed`),
			config: Config{
				DeviceLabels: map[string]prometheus.Labels{},
				Subscriptions: []string{
					"/Sysdb/environment/cooling/status",
					"/Sysdb/environment/power/status",
				},
				Metrics: []*MetricDef{
					{
						Path: "/Sysdb/(?:lag|slice/phy/.+)/intfCounterDir/(?P<intf>.+)/intfCounter",
						re: regexp.MustCompile(
							"/Sysdb/(?:lag|slice/phy/.+)/intfCounterDir/(?P<intf>.+)/intfCounter"),
						Name:    "intfCounter",
						Help:    "Per-Interface Bytes/Errors/Discards Counters",
						devDesc: map[string]*promDesc{},
						desc: &promDesc{
							fqName:    "intfCounter",
							help:      "Per-Interface Bytes/Errors/Discards Counters",
							varLabels: []string{"intf"},
						},
					},
					{
						Path: "/Sysdb/environment/cooling/fan/speed/value",
						re: regexp.MustCompile(
							"/Sysdb/environment/cooling/fan/speed/value"),
						Name:    "fanSpeed",
						Help:    "Fan Speed",
						devDesc: map[string]*promDesc{},
						desc:    &promDesc{fqName: "fanSpeed", help: "Fan Speed"},
					}},
				subsByOrigin: map[string][]string{
					"": []string{
						"/Sysdb/environment/cooling/status",
						"/Sysdb/environment/power/status",
					},
				},
			},
		},
	}

	for i, c := range tCases {
		cfg, err := parseConfig(c.input)
		if err != nil {
			t.Errorf("Unexpected error in case %d: %v", i+1, err)
			continue
		}
		if !test.DeepEqual(*cfg, c.config) {
			t.Errorf("Test case %d: mismatch %v", i+1, test.Diff(*cfg, c.config))
		}
	}
}

func TestGetMetricValues(t *testing.T) {
	config := []byte(`
devicelabels:
        10.1.1.1:
                lab1: val1
                lab2: val2
        '*':
                lab1: val3
                lab2: val4
subscriptions:
        - /Sysdb/environment/cooling/status
        - /Sysdb/environment/power/status
metrics:
        - name: intfCounter
          path: /Sysdb/(lag|slice/phy/.+)/intfCounterDir/(?P<intf>.+)/intfCounter
          help: Per-Interface Bytes/Errors/Discards Counters
        - name: fanSpeed
          path: /Sysdb/environment/cooling/status/fan/speed/value
          help: Fan Speed`)
	cfg, err := parseConfig(config)
	if err != nil {
		t.Fatalf("Unexpected error: %v", err)
	}

	tCases := []struct {
		src    source
		desc   *prometheus.Desc
		labels []string
	}{
		{
			src: source{
				addr: "10.1.1.1",
				path: "/Sysdb/lag/intfCounterDir/Ethernet1/intfCounter",
			},
			desc: prometheus.NewDesc("intfCounter", "Per-Interface Bytes/Errors/Discards Counters",
				[]string{"unnamedLabel1", "intf"},
				prometheus.Labels{"lab1": "val1", "lab2": "val2"}),
			labels: []string{"lag", "Ethernet1"},
		},
		{
			src: source{
				addr: "10.2.2.2",
				path: "/Sysdb/lag/intfCounterDir/Ethernet1/intfCounter",
			},
			desc: prometheus.NewDesc("intfCounter", "Per-Interface Bytes/Errors/Discards Counters",
				[]string{"unnamedLabel1", "intf"},
				prometheus.Labels{"lab1": "val3", "lab2": "val4"}),
			labels: []string{"lag", "Ethernet1"},
		},
		{
			src: source{
				addr: "10.2.2.2",
				path: "/Sysdb/environment/cooling/status/fan/speed/value",
			},
			desc: prometheus.NewDesc("fanSpeed", "Fan Speed",
				[]string{},
				prometheus.Labels{"lab1": "val3", "lab2": "val4"}),
			labels: []string{},
		},
		{
			src: source{
				addr: "10.2.2.2",
				path: "/Sysdb/environment/nonexistent",
			},
			desc:   nil,
			labels: nil,
		},
	}
	descLabels := make(map[string]map[string]string)
	for i, c := range tCases {
		metric := cfg.getMetricValues(c.src, descLabels)
		if metric == nil {
			// Avoids error from trying to access metric.desc when metric is nil
			metric = &metricValues{}
		}
		if !test.DeepEqual(metric.desc, c.desc) {
			t.Errorf("Test case %d: desc mismatch %v", i+1, test.Diff(metric.desc, c.desc))
		}
		if !test.DeepEqual(metric.labels, c.labels) {
			t.Errorf("Test case %d: labels mismatch %v", i+1, test.Diff(metric.labels, c.labels))
		}
	}
}

func TestGetAllDescs(t *testing.T) {
	tCases := []struct {
		config []byte
		descs  []*prometheus.Desc
	}{
		{
			config: []byte(`
devicelabels:
        10.1.1.1:
                lab1: val1
                lab2: val2
        '*':
                lab1: val3
                lab2: val4
subscriptions:
        - /Sysdb/environment/cooling/status
        - /Sysdb/environment/power/status
metrics:
        - name: intfCounter
          path: /Sysdb/(lag|slice/phy/.+)/intfCounterDir/(?P<intf>.+)/intfCounter
          help: Per-Interface Bytes/Errors/Discards Counters
        - name: fanSpeed
          path: /Sysdb/environment/cooling/status/fan/speed/value
          help: Fan Speed`),
			descs: []*prometheus.Desc{
				prometheus.NewDesc("intfCounter", "Per-Interface Bytes/Errors/Discards Counters",
					[]string{"unnamedLabel1", "intf"},
					prometheus.Labels{"lab1": "val3", "lab2": "val4"}),
				prometheus.NewDesc("intfCounter", "Per-Interface Bytes/Errors/Discards Counters",
					[]string{"unnamedLabel1", "intf"},
					prometheus.Labels{"lab1": "val1", "lab2": "val2"}),
				prometheus.NewDesc("fanSpeed", "Fan Speed", []string{},
					prometheus.Labels{"lab1": "val3", "lab2": "val4"}),
				prometheus.NewDesc("fanSpeed", "Fan Speed", []string{},
					prometheus.Labels{"lab1": "val1", "lab2": "val2"}),
			},
		},
		{
			config: []byte(`
devicelabels:
        10.1.1.1:
                lab1: val1
                lab2: val2
subscriptions:
        - /Sysdb/environment/cooling/status
        - /Sysdb/environment/power/status
metrics:
        - name: intfCounter
          path: /Sysdb/(lag|slice/phy/.+)/intfCounterDir/(?P<intf>.+)/intfCounter
          help: Per-Interface Bytes/Errors/Discards Counters
        - name: fanSpeed
          path: /Sysdb/environment/cooling/status/fan/speed/value
          help: Fan Speed`),
			descs: []*prometheus.Desc{
				prometheus.NewDesc("intfCounter", "Per-Interface Bytes/Errors/Discards Counters",
					[]string{"unnamedLabel1", "intf"},
					prometheus.Labels{"lab1": "val1", "lab2": "val2"}),
				prometheus.NewDesc("fanSpeed", "Fan Speed", []string{},
					prometheus.Labels{"lab1": "val1", "lab2": "val2"}),
			},
		},
	}

	for i, c := range tCases {
		cfg, err := parseConfig(c.config)
		if err != nil {
			t.Fatalf("Unexpected error: %v", err)
		}

		ch := make(chan *prometheus.Desc, 10)
		cfg.getAllDescs(ch)
		j := 0
		for d := range ch {
			if !test.DeepEqual(c.descs[j], d) {
				t.Errorf("Test case %d: desc %d mismatch %v", i+1, j+1, test.Diff(c.descs[j], d))
			}
			j++
			if j == len(c.descs) {
				break
			}
		}
		select {
		case <-ch:
			t.Errorf("Test case %d: too many descs", i+1)
		default:
		}
	}
}
