This repository has no description
1package models
2
3import (
4 "encoding/json"
5 "os"
6 "path/filepath"
7 "strings"
8 "testing"
9)
10
11func testWorkflowId(name string) WorkflowId {
12 return WorkflowId{PipelineId: PipelineId{Knot: "knot1", Rkey: "rkey1"}, Name: name}
13}
14
15func readDataContents(t *testing.T, path string) []string {
16 t.Helper()
17 raw, err := os.ReadFile(path)
18 if err != nil {
19 t.Fatal(err)
20 }
21
22 var got []string
23 for _, encoded := range strings.Split(strings.TrimSpace(string(raw)), "\n") {
24 var line LogLine
25 if err := json.Unmarshal([]byte(encoded), &line); err != nil {
26 t.Fatalf("decode log line %q: %v", encoded, err)
27 }
28 got = append(got, line.Content)
29 }
30 return got
31}
32
33func TestDataWriterMasksSecretSplitAcrossWrites(t *testing.T) {
34 dir := t.TempDir()
35 secret := "hunter2-super-secret-token"
36 wid := testWorkflowId("mask")
37 logger, err := NewFileWorkflowLogger(dir, wid, []string{secret})
38 if err != nil {
39 t.Fatal(err)
40 }
41 w := logger.DataWriter(0, "stdout")
42
43 for _, ch := range strings.Split("prefix "+secret+" suffix", "") {
44 if _, err := w.Write([]byte(ch)); err != nil {
45 t.Fatal(err)
46 }
47 }
48 if err := logger.Close(); err != nil {
49 t.Fatal(err)
50 }
51
52 raw, err := os.ReadFile(filepath.Join(dir, wid.String()+".log"))
53 if err != nil {
54 t.Fatal(err)
55 }
56 if strings.Contains(string(raw), secret) {
57 t.Errorf("log contains raw secret: %s", raw)
58 }
59 if !strings.Contains(string(raw), "***") {
60 t.Errorf("log does not contain masked marker: %s", raw)
61 }
62 // trailing bytes land in the final flush entry contiguously
63 if !strings.Contains(string(raw), "suffix") {
64 t.Errorf("log lost trailing output: %s", raw)
65 }
66 if got := strings.Join(readDataContents(t, filepath.Join(dir, wid.String()+".log")), "\n"); got != "prefix *** suffix" {
67 t.Errorf("masked output changed: %q", got)
68 }
69}
70
71func TestDataWriterMasksSingleFrame(t *testing.T) {
72 dir := t.TempDir()
73 secret := "hunter2-super-secret-token"
74 wid := testWorkflowId("frame")
75 logger, err := NewFileWorkflowLogger(dir, wid, []string{secret})
76 if err != nil {
77 t.Fatal(err)
78 }
79 w := logger.DataWriter(0, "stdout")
80 if _, err := w.Write([]byte("token is " + secret + " ok")); err != nil {
81 t.Fatal(err)
82 }
83 if err := logger.Close(); err != nil {
84 t.Fatal(err)
85 }
86
87 raw, err := os.ReadFile(filepath.Join(dir, wid.String()+".log"))
88 if err != nil {
89 t.Fatal(err)
90 }
91 if strings.Contains(string(raw), secret) {
92 t.Errorf("log contains raw secret: %s", raw)
93 }
94 if !strings.Contains(string(raw), "en is *** ok") {
95 t.Errorf("masked entry mangled: %s", raw)
96 }
97}
98
99func TestDataWriterNoMaskPassthrough(t *testing.T) {
100 dir := t.TempDir()
101 wid := testWorkflowId("plain")
102 logger, err := NewFileWorkflowLogger(dir, wid, nil)
103 if err != nil {
104 t.Fatal(err)
105 }
106 w := logger.DataWriter(0, "stdout")
107 if _, err := w.Write([]byte("hello")); err != nil {
108 t.Fatal(err)
109 }
110 if _, err := w.Write([]byte(" world")); err != nil {
111 t.Fatal(err)
112 }
113 if err := logger.Close(); err != nil {
114 t.Fatal(err)
115 }
116
117 raw, err := os.ReadFile(filepath.Join(dir, wid.String()+".log"))
118 if err != nil {
119 t.Fatal(err)
120 }
121 if !strings.Contains(string(raw), "hello") || !strings.Contains(string(raw), " world") {
122 t.Errorf("log missing output: %s", raw)
123 }
124}
125
126func TestDataWriterDoesNotSplitSafeFragmentsIntoLogLines(t *testing.T) {
127 dir := t.TempDir()
128 wid := testWorkflowId("line-boundaries")
129 logger, err := NewFileWorkflowLogger(dir, wid, []string{"a-secret-with-a-long-window"})
130 if err != nil {
131 t.Fatal(err)
132 }
133 w := logger.DataWriter(0, "stdout")
134 want := []string{
135 "first line",
136 "second line",
137 "third line",
138 "fourth line",
139 "fifth line",
140 "sixth line",
141 "seventh line",
142 "eighth line",
143 }
144 for _, line := range want {
145 if _, err := w.Write([]byte(line + "\n")); err != nil {
146 t.Fatal(err)
147 }
148 }
149 if err := logger.Close(); err != nil {
150 t.Fatal(err)
151 }
152
153 got := readDataContents(t, filepath.Join(dir, wid.String()+".log"))
154
155 if joined := strings.Join(got, "\n"); joined != strings.Join(want, "\n") {
156 t.Fatalf("log content was split at masking window:\n got: %q\nwant: %q", joined, strings.Join(want, "\n"))
157 }
158}
159
160func TestDataWriterMasksMultilineSecret(t *testing.T) {
161 dir := t.TempDir()
162 secret := "line-one\nline-two"
163 wid := testWorkflowId("multiline-mask")
164 logger, err := NewFileWorkflowLogger(dir, wid, []string{secret})
165 if err != nil {
166 t.Fatal(err)
167 }
168 w := logger.DataWriter(0, "stdout")
169 chunk := strings.Repeat("p", 40) + "\nline-one\nline-two\n" + strings.Repeat("t", 30) + "\nsuffix\n"
170 if _, err := w.Write([]byte(chunk)); err != nil {
171 t.Fatal(err)
172 }
173 if err := logger.Close(); err != nil {
174 t.Fatal(err)
175 }
176
177 got := strings.Join(readDataContents(t, filepath.Join(dir, wid.String()+".log")), "\n")
178 want := strings.Repeat("p", 40) + "\n***\n***\n" + strings.Repeat("t", 30) + "\nsuffix"
179 if got != want {
180 t.Fatalf("multiline secret was not masked: %q", got)
181 }
182}