summaryrefslogtreecommitdiff
path: root/internal/color/brush/brush_test.go
blob: 52f31c27e2c3beb4cc88183623754ddf84383445 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
package brush

import (
	"os"
	"strings"
	"testing"

	"github.com/mimecast/dtail/internal/config"
	"github.com/mimecast/dtail/internal/source"
)

// TestMain ensures config.Client is populated with defaults so Colorfy can
// reach the colourised branches without nil-dereferencing TermColors fields.
func TestMain(m *testing.M) {
	config.Setup(source.Client, &config.Args{ConfigFile: "none"}, nil)
	os.Exit(m.Run())
}

// TestColorfy_ShortFramesDoNotPanic feeds Colorfy with malformed or short
// protocol frames (e.g. "REMOTE", "REMOTE|host", "CLIENT|host") which used
// to cause an index-out-of-range panic in paintRemote/paintClient/paintServer
// when they unconditionally indexed splitted[0..N] after SplitN.
// For any short frame Colorfy must not panic and must return a non-empty
// string that still contains the original text (either verbatim in the plain
// fallback branch or embedded inside color escape sequences).
func TestColorfy_ShortFramesDoNotPanic(t *testing.T) {
	cases := []struct {
		name string
		in   string
	}{
		{"empty", ""},
		{"plain_text", "hello world"},
		{"remote_prefix_only", "REMOTE"},
		{"remote_one_field", "REMOTE|host"},
		{"remote_two_fields", "REMOTE|host|100"},
		{"remote_three_fields", "REMOTE|host|100|1"},
		{"remote_four_fields", "REMOTE|host|100|1|id"},
		{"remote_full_frame", "REMOTE|host|100|1|id|hello from remote"},
		{"client_prefix_only", "CLIENT"},
		{"client_one_field", "CLIENT|host"},
		{"client_full_frame", "CLIENT|host|hello from client"},
		{"server_prefix_only", "SERVER"},
		{"server_one_field", "SERVER|host"},
		{"server_full_frame", "SERVER|host|hello from server"},
	}

	for _, tc := range cases {
		tc := tc
		t.Run(tc.name, func(t *testing.T) {
			defer func() {
				if r := recover(); r != nil {
					t.Fatalf("Colorfy(%q) panicked: %v", tc.in, r)
				}
			}()

			got := Colorfy(tc.in)

			if tc.in == "" {
				// An empty input must not suddenly produce any content.
				if got != "" && !strings.Contains(got, "") {
					t.Fatalf("Colorfy(\"\") = %q, want empty or trivial", got)
				}
				return
			}

			// The original payload should still be recoverable somewhere in
			// the output so users can see what the server actually sent even
			// when the frame is malformed.
			if !strings.Contains(got, tc.in) && !containsAllFields(got, tc.in) {
				t.Fatalf("Colorfy(%q) = %q, original payload not present", tc.in, got)
			}
		})
	}
}

// containsAllFields checks whether every non-delimiter field of the input is
// present in the output. This allows the colorised happy path (which
// interleaves escape sequences between fields) to still satisfy the
// "payload preserved" expectation.
func containsAllFields(out, in string) bool {
	for _, f := range strings.Split(in, "|") {
		if f == "" {
			continue
		}
		if !strings.Contains(out, f) {
			return false
		}
	}
	return true
}