Ctrl K
ComunidadGuías…

integration/check_test.go

Rama master · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
//go:build integration

package integration

import (
	"bufio"
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"strconv"
	"strings"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

// absent, as an expected header value, means the header must not be present.
const absent = "\x00absent"

type (
	h = map[string]string
	j = map[string]string
)

// tc is one table-driven case: a single GET against the plain gateway, and
// everything expected of that one response.
type tc struct {
	name      string
	path      string
	reqHeader h
	want      expect
}

type expect struct {
	status       int
	header       h
	json         j
	noJSON       []string
	bodyContains string
}

func runCases(t *testing.T, cases []tc) {
	t.Helper()

	for _, c := range cases {
		t.Run(c.name, func(t *testing.T) {
			t.Parallel()

			resp, body := get(t, env.client, env.base+c.path, c.reqHeader)
			c.want.check(t, resp, body)
		})
	}
}

func (e expect) check(t *testing.T, resp *http.Response, body []byte) {
	t.Helper()

	if e.status != 0 {
		assert.Equal(t, e.status, resp.StatusCode, "status; body: %s", body)
	}

	for name, want := range e.header {
		if want == absent {
			assert.NotContains(t, resp.Header, http.CanonicalHeaderKey(name), "header %s must be absent", name)
			continue
		}

		assert.Equal(t, want, resp.Header.Get(name), "header %s", name)
	}

	if e.bodyContains != "" {
		assert.Contains(t, string(body), e.bodyContains)
	}

	if len(e.json) == 0 && len(e.noJSON) == 0 {
		return
	}

	var doc any
	require.NoError(t, json.Unmarshal(body, &doc), "body is not JSON: %s", body)

	for path, want := range e.json {
		got, ok := lookup(doc, path)
		if assert.True(t, ok, "json path %q missing; body: %s", path, body) {
			assert.Equal(t, want, fmt.Sprint(got), "json path %q", path)
		}
	}

	for _, path := range e.noJSON {
		_, ok := lookup(doc, path)
		assert.False(t, ok, "json path %q must be absent; body: %s", path, body)
	}
}

func lookup(doc any, path string) (any, bool) {
	cur := doc

	for seg := range strings.SplitSeq(path, ".") {
		switch v := cur.(type) {
		case map[string]any:
			if seg == "#" {
				return len(v), true
			}

			next, ok := v[seg]
			if !ok {
				return nil, false
			}

			cur = next
		case []any:
			if seg == "#" {
				return len(v), true
			}

			i, err := strconv.Atoi(seg)
			if err != nil || i < 0 || i >= len(v) {
				return nil, false
			}

			cur = v[i]
		default:
			return nil, false
		}
	}

	return cur, true
}

// get performs a GET and returns the response with its fully read body.
func get(t *testing.T, client *http.Client, url string, reqHeader h) (*http.Response, []byte) {
	t.Helper()

	resp, body, err := tryGet(t, client, url, reqHeader)
	require.NoError(t, err)

	return resp, body
}

// tryGet is get for cases where the request itself is expected to fail.
func tryGet(t *testing.T, client *http.Client, url string, reqHeader h) (*http.Response, []byte, error) {
	t.Helper()

	req, err := http.NewRequestWithContext(t.Context(), http.MethodGet, url, nil)
	require.NoError(t, err)

	for k, v := range reqHeader {
		req.Header.Set(k, v)
	}

	resp, err := client.Do(req)
	if err != nil {
		return nil, nil, err
	}
	defer resp.Body.Close()

	body, err := io.ReadAll(resp.Body)

	return resp, body, err
}

func expectStatus(t *testing.T, path string, status int) {
	t.Helper()

	resp, body := get(t, env.client, env.base+path, nil)
	assert.Equal(t, status, resp.StatusCode, "GET %s; body: %s", path, body)
}

// countEvents counts SSE "data:" lines in a stream body.
func countEvents(body []byte) int {
	n := 0

	sc := bufio.NewScanner(bytes.NewReader(body))
	for sc.Scan() {
		if strings.HasPrefix(sc.Text(), "data:") {
			n++
		}
	}

	return n
}