<commit_before>package main

import (
	"encoding/base64"
	"fmt"
	"github.com/gesellix/couchdb-exporter/lib"
	"github.com/golang/protobuf/proto"
	"github.com/prometheus/client_golang/prometheus"
	dto "github.com/prometheus/client_model/go"
	"io/ioutil"
	"net/http"
	"net/http/httptest"
	"sort"
	"strings"
	"testing"
)

type handler func(w http.ResponseWriter, r *http.Request)

func BasicAuth(basicAuth lib.BasicAuth, pass handler) handler {

	validate := func(basicAuth lib.BasicAuth, username, password string) bool {
		if username == basicAuth.Username && password == <PASSWORD> {
			return true
		}
		return false
	}

	return func(w http.ResponseWriter, r *http.Request) {

		if len(r.Header["Authorization"]) == 0 || len(r.Header["Authorization"][0]) == 0 {
			http.Error(w, "missing Authorization", http.StatusBadRequest)
			return
		}
		auth := strings.SplitN(r.Header["Authorization"][0], " ", 2)

		if len(auth) != 2 || auth[0] != "Basic" {
			http.Error(w, "bad syntax", http.StatusBadRequest)
			return
		}

		payload, _ := base64.StdEncoding.DecodeString(auth[1])
		pair := strings.SplitN(string(payload), ":", 2)

		if len(pair) != 2 || !validate(basicAuth, pair[0], pair[1]) {
			http.Error(w, "authorization failed", http.StatusUnauthorized)
			return
		}

		pass(w, r)
	}
}

func readFile(t *testing.T, filename string) []byte {
	fileContent, err := ioutil.ReadFile(filename)
	if err != nil {
		t.Errorf("Error reading file %s: %v\n", filename, err)
	}
	return fileContent
}

func couchdbResponse(t *testing.T, versionSuffix string) handler {
	return func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Path == "/" {
			file := readFile(t, fmt.Sprintf("./testdata/couchdb-%s.json", versionSuffix))
			w.Write([]byte(file))
		} else if r.URL.Path == "/_membership" {
			file := readFile(t, fmt.Sprintf("./testdata/couchdb-membership-response-%s.json", versionSuffix))
			w.Write([]byte(file))
		} else if r.URL.Path == "/example" {
			file := readFile(t, fmt.Sprintf("./testdata/example-meta-%s.json", versionSuffix))
			w.Write([]byte(file))
		} else if r.URL.Path == "/another-example" {
			file := readFile(t, fmt.Sprintf("./testdata/example-meta-%s.json", versionSuffix))
			w.Write([]byte(file))
		} else {
			file := readFile(t, fmt.Sprintf("./testdata/couchdb-stats-response-%s.json", versionSuffix))
			w.Write([]byte(file))
		}
	}
}

func getGaugeValue(metrics []*dto.Metric, labelName string, labelValue string) float64 {
	for _, metric := range metrics {
		for _, label := range metric.Label {
			if *label.Name == labelName && *label.Value == labelValue {
				return *metric.Gauge.Value
			}
		}
	}
	return 0
}

func printMetrics(metrics []*dto.Metric) {
	metricStrings := []string{}
	for _, metric := range metrics {
		metricStrings = append(metricStrings, proto.CompactTextString(metric))
	}

	sort.Strings(metricStrings)
	fmt.Println(metricStrings)
}

func performCouchdbStatsTest(t *testing.T, couchdbVersion string, expectedMetricsCount int, expectedGetRequestCount float64, expectedDiskSize float64) {
	basicAuth := lib.BasicAuth{Username: "username", Password: "password"}
	handler := http.HandlerFunc(BasicAuth(basicAuth, couchdbResponse(t, couchdbVersion)))
	server := httptest.NewServer(handler)

	e := lib.NewExporter(server.URL, basicAuth, []string{"example"})
	ch := make(chan prometheus.Metric)

	go func() {
		defer close(ch)
		e.Collect(ch)
	}()

	metrics := []*dto.Metric{}
	for metric := range ch {
		dtoMetric := &dto.Metric{}
		metric.Write(dtoMetric)
		metrics = append(metrics, dtoMetric)
	}
	//printMetrics(metrics)
	actualGetRequestCount := getGaugeValue(metrics, "method", "GET")
	actualDiskSize := getGaugeValue(metrics, "db_name", "example")

	if len(metrics) < expectedMetricsCount {
		t.Errorf("got less metrics (%d) as expected (%d)", len(metrics), expectedMetricsCount)
	}
	if len(metrics) > expectedMetricsCount {
		t.Errorf("got more metrics (%d) as expected (%d)", len(metrics), expectedMetricsCount)
	}
	if expectedGetRequestCount != actualGetRequestCount {
		t.Errorf("expected %d GET requests, but got %d instead", expectedGetRequestCount, actualGetRequestCount)
	}
	if expectedDiskSize != actualDiskSize {
		t.Errorf("expected %f disk size, but got %f instead", expectedDiskSize, actualDiskSize)
	}
}

func TestCouchdbStatsV1(t *testing.T) {
	// expectedMetricsCount := count(nodes) * 34 + 1
	performCouchdbStatsTest(t, "v1", 35, 4711, 12396)
}

func TestCouchdbStatsV2(t *testing.T) {
	performCouchdbStatsTest(t, "v2", 69, 4712, 58570)
}<commit_msg>test reading more than a single db.<commit_after>package main

import (
	"encoding/base64"
	"fmt"
	"github.com/gesellix/couchdb-exporter/lib"
	"github.com/golang/protobuf/proto"
	"github.com/prometheus/client_golang/prometheus"
	dto "github.com/prometheus/client_model/go"
	"io/ioutil"
	"net/http"
	"net/http/httptest"
	"sort"
	"strings"
	"testing"
)

type handler func(w http.ResponseWriter, r *http.Request)

func BasicAuth(basicAuth lib.BasicAuth, pass handler) handler {

	validate := func(basicAuth lib.BasicAuth, username, password string) bool {
		if username == basicAuth.Username && password == basicAuth.Password {
			return true
		}
		return false
	}

	return func(w http.ResponseWriter, r *http.Request) {

		if len(r.Header["Authorization"]) == 0 || len(r.Header["Authorization"][0]) == 0 {
			http.Error(w, "missing Authorization", http.StatusBadRequest)
			return
		}
		auth := strings.SplitN(r.Header["Authorization"][0], " ", 2)

		if len(auth) != 2 || auth[0] != "Basic" {
			http.Error(w, "bad syntax", http.StatusBadRequest)
			return
		}

		payload, _ := base64.StdEncoding.DecodeString(auth[1])
		pair := strings.SplitN(string(payload), ":", 2)

		if len(pair) != 2 || !validate(basicAuth, pair[0], pair[1]) {
			http.Error(w, "authorization failed", http.StatusUnauthorized)
			return
		}

		pass(w, r)
	}
}

func readFile(t *testing.T, filename string) []byte {
	fileContent, err := ioutil.ReadFile(filename)
	if err != nil {
		t.Errorf("Error reading file %s: %v\n", filename, err)
	}
	return fileContent
}

func couchdbResponse(t *testing.T, versionSuffix string) handler {
	return func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Path == "/" {
			file := readFile(t, fmt.Sprintf("./testdata/couchdb-%s.json", versionSuffix))
			w.Write([]byte(file))
		} else if r.URL.Path == "/_membership" {
			file := readFile(t, fmt.Sprintf("./testdata/couchdb-membership-response-%s.json", versionSuffix))
			w.Write([]byte(file))
		} else if r.URL.Path == "/example" {
			file := readFile(t, fmt.Sprintf("./testdata/example-meta-%s.json", versionSuffix))
			w.Write([]byte(file))
		} else if r.URL.Path == "/another-example" {
			file := readFile(t, fmt.Sprintf("./testdata/example-meta-%s.json", versionSuffix))
			w.Write([]byte(file))
		} else {
			file := readFile(t, fmt.Sprintf("./testdata/couchdb-stats-response-%s.json", versionSuffix))
			w.Write([]byte(file))
		}
	}
}

func getGaugeValue(metrics []*dto.Metric, labelName string, labelValue string) float64 {
	for _, metric := range metrics {
		for _, label := range metric.Label {
			if *label.Name == labelName && *label.Value == labelValue {
				return *metric.Gauge.Value
			}
		}
	}
	return 0
}

func printMetrics(metrics []*dto.Metric) {
	metricStrings := []string{}
	for _, metric := range metrics {
		metricStrings = append(metricStrings, proto.MarshalTextString(metric))
	}

	sort.Strings(metricStrings)
	fmt.Println(metricStrings)
}

func performCouchdbStatsTest(t *testing.T, couchdbVersion string, expectedMetricsCount int, expectedGetRequestCount float64, expectedDiskSize float64) {
	basicAuth := lib.BasicAuth{Username: "username", Password: "password"}
	handler := http.HandlerFunc(BasicAuth(basicAuth, couchdbResponse(t, couchdbVersion)))
	server := httptest.NewServer(handler)

	e := lib.NewExporter(server.URL, basicAuth, []string{"example", "another-example"})
	ch := make(chan prometheus.Metric)

	go func() {
		defer close(ch)
		e.Collect(ch)
	}()

	metrics := []*dto.Metric{}
	for metric := range ch {
		dtoMetric := &dto.Metric{}
		metric.Write(dtoMetric)
		metrics = append(metrics, dtoMetric)
	}
	//printMetrics(metrics)
	actualGetRequestCount := getGaugeValue(metrics, "method", "GET")
	actualDiskSize := getGaugeValue(metrics, "db_name", "example")

	if len(metrics) < expectedMetricsCount {
		t.Errorf("got less metrics (%d) as expected (%d)", len(metrics), expectedMetricsCount)
	}
	if len(metrics) > expectedMetricsCount {
		t.Errorf("got more metrics (%d) as expected (%d)", len(metrics), expectedMetricsCount)
	}
	if expectedGetRequestCount != actualGetRequestCount {
		t.Errorf("expected %d GET requests, but got %d instead", expectedGetRequestCount, actualGetRequestCount)
	}
	if expectedDiskSize != actualDiskSize {
		t.Errorf("expected %f disk size, but got %f instead", expectedDiskSize, actualDiskSize)
	}
}

func TestCouchdbStatsV1(t *testing.T) {
	// expectedMetricsCount := count(nodes) * 34 + 1
	performCouchdbStatsTest(t, "v1", 38, 4711, 12396)
}

func TestCouchdbStatsV2(t *testing.T) {
	performCouchdbStatsTest(t, "v2", 75, 4712, 58570)
}
{'fraction_non_alphanumeric': 0.1564528550995709, 'fraction_numerical': 0.011442402904609968, 'mean_word_length': 4.749525616698293, 'pattern_counts': {'":': 2, '<': 6, '<?xml version=': 0, '>': 6, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6240037', 'n_tokens_mistral': 3358, 'n_tokens_neox': 3103, 'n_words': 659}