Ctrl K
ComunidadGuías…

pkg/util_ok_test.go

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

Archivo original ↗Preguntar a KiBot
package pkg

import (
	"errors"
	"io"
	"strings"
	"testing"

	"go.keploy.io/server/v3/pkg/models"
	"go.keploy.io/server/v3/pkg/provenswitch"
	"go.uber.org/zap"
)

// frames is a recorded stream of chunks, each one field.
func frames(key string, values ...string) []models.HTTPStreamChunk {
	out := make([]models.HTTPStreamChunk, len(values))
	for i, v := range values {
		out[i] = models.HTTPStreamChunk{Data: []models.HTTPStreamDataField{{Key: key, Value: v}}}
	}
	return out
}

// Under the proven switch a streamed answer is read to its end, and a frame
// after the recorded ones is a difference (O-K): the comparison stopped at
// the last recorded frame, so a stream that went on (an error event after the
// answer, a second answer) passed. Without the switch it stops there, as
// before. A stream that ends with the recording passes either way.
func TestCompareHTTPStream_ProvenSwitchReadsTheStreamToItsEnd(t *testing.T) {
	for _, tc := range []struct {
		name     string
		cfg      HTTPStreamConfig
		expected models.HTTPResp
		same     string
		// goesOn is the stream that goes on after the recorded frames, in
		// the reads it arrives in: the trailing data comes in a read of its
		// own, as it does from a server that writes it later.
		goesOn []string
	}{
		{
			name:     "SSE",
			cfg:      HTTPStreamConfig{Mode: HTTPStreamModeSSE},
			expected: models.HTTPResp{StreamBody: frames("data", "a", "b")},
			same:     "data: a\n\ndata: b\n\n",
			goesOn:   []string{"data: a\n\ndata: b\n\n", "event: error\ndata: boom\n\n"},
		},
		{
			name:     "NDJSON",
			cfg:      HTTPStreamConfig{Mode: HTTPStreamModeNDJSON},
			expected: models.HTTPResp{StreamBody: frames("raw", `{"id":1}`, `{"id":2}`)},
			same:     "{\"id\":1}\n{\"id\":2}\n",
			goesOn:   []string{"{\"id\":1}\n{\"id\":2}\n", "{\"error\":\"boom\"}\n"},
		},
		{
			name:     "plain text",
			cfg:      HTTPStreamConfig{Mode: HTTPStreamModePlainText},
			expected: models.HTTPResp{StreamBody: frames("raw", "line 1", "line 2")},
			same:     "line 1\nline 2\n",
			goesOn:   []string{"line 1\nline 2\n", "line 3\n"},
		},
		{
			name:     "binary",
			cfg:      HTTPStreamConfig{Mode: HTTPStreamModeBinary},
			expected: models.HTTPResp{StreamBody: frames("raw", "abcd")},
			same:     "abcd",
			goesOn:   []string{"abcd", "ef"},
		},
		{
			name:     "multipart",
			cfg:      HTTPStreamConfig{Mode: HTTPStreamModeMultipart, Boundary: "b"},
			expected: models.HTTPResp{Body: "--b\r\nContent-Type: text/plain\r\n\r\none\r\n--b--\r\n"},
			same:     "--b\r\nContent-Type: text/plain\r\n\r\none\r\n--b--\r\n",
			goesOn:   []string{"--b\r\nContent-Type: text/plain\r\n\r\none\r\n", "--b\r\nContent-Type: text/plain\r\n\r\ntwo\r\n--b--\r\n"},
		},
	} {
		t.Run(tc.name, func(t *testing.T) {
			compare := func(reads ...string) (bool, *StreamMismatchInfo) {
				parts := make([]io.Reader, len(reads))
				for i, r := range reads {
					parts[i] = strings.NewReader(r)
				}
				ok, _, info, err := CompareHTTPStream(tc.expected, io.MultiReader(parts...), tc.cfg, map[string]struct{}{}, zap.NewNop())
				if err != nil {
					t.Fatalf("compare: %v", err)
				}
				return ok, info
			}

			t.Setenv(provenswitch.Env, "")
			if ok, _ := compare(tc.same); !ok {
				t.Fatal("control: the recorded stream does not match without the switch")
			}
			if ok, _ := compare(tc.goesOn...); !ok {
				t.Fatal("control: without the switch the comparison stops at the last recorded frame, as before")
			}

			t.Setenv(provenswitch.Env, "1")
			if ok, info := compare(tc.same); !ok {
				t.Fatalf("KEPLOY_PROVEN=1: the recorded stream did not match: %+v", info)
			}
			ok, info := compare(tc.goesOn...)
			if ok {
				t.Fatal("KEPLOY_PROVEN=1: a stream that went on after its recorded frames matched")
			}
			if info == nil {
				t.Fatal("KEPLOY_PROVEN=1: the trailing frame is not named")
			}
		})
	}
}

// Under the proven switch a keepalive after the recorded frames (an SSE frame
// of comments alone) is not a difference: it carries no event. It is one
// beyond the recording, so the read ends on it (row W1): the app kept the
// stream open, and the test has no verdict (ErrStreamHeldOpen), never a pass,
// whatever the app would have sent after it. An event after the recorded
// frames still is a difference, a comment beside it or not.
func TestCompareHTTPStream_ProvenSwitchEndsOnATrailingSSEKeepalive(t *testing.T) {
	t.Setenv(provenswitch.Env, "1")
	expected := models.HTTPResp{StreamBody: frames("data", "a", "b")}
	compare := func(body string) (bool, *StreamMismatchInfo, error) {
		ok, _, info, err := CompareHTTPStream(expected, strings.NewReader(body), HTTPStreamConfig{Mode: HTTPStreamModeSSE}, map[string]struct{}{}, zap.NewNop())
		return ok, info, err
	}
	for _, body := range []string{"data: a\n\ndata: b\n\n: ping\n\n", "data: a\n\ndata: b\n\n: ping\n\nevent: error\ndata: boom\n\n"} {
		if ok, info, err := compare(body); !ok || info != nil || !errors.Is(err, ErrStreamHeldOpen) {
			t.Fatalf("%q: %v, %+v, %v; want the recorded frames matched and ErrStreamHeldOpen", body, ok, info, err)
		}
	}
	if ok, _, err := compare("data: a\n\ndata: b\n\n: ping\ndata: boom\n\n"); ok || err != nil {
		t.Fatalf("a frame with a comment and an event after the recorded frames gave %v, %v; want a difference", ok, err)
	}
}