1package docker
2
3import (
4 "archive/tar"
5 "bytes"
6 "context"
7 "encoding/binary"
8 "io"
9 "strings"
10 "testing"
11
12 "github.com/stretchr/testify/assert"
13 "github.com/stretchr/testify/require"
14
15 "github.com/bnema/gordon/internal/domain"
16)
17
18func TestParseExecOutput_SplitsStdoutAndStderr(t *testing.T) {
19 stream := append(frameDockerStream(1, []byte("hello\n")), frameDockerStream(2, []byte("warn\n"))...)
20
21 stdout, stderr, err := parseExecOutput(bytes.NewReader(stream))
22
23 require.NoError(t, err)
24 assert.Equal(t, []byte("hello\n"), stdout)
25 assert.Equal(t, []byte("warn\n"), stderr)
26}
27
28func TestParseExecOutput_RejectsOversizedFrameBeforeReadingPayload(t *testing.T) {
29 var header [8]byte
30 header[0] = 1
31 binary.BigEndian.PutUint32(header[4:], maxExecOutputSize+1)
32
33 stdout, stderr, err := parseExecOutput(bytes.NewReader(header[:]))
34
35 assert.ErrorIs(t, err, domain.ErrExecOutputExceeded)
36 assert.Nil(t, stdout)
37 assert.Nil(t, stderr)
38}
39
40func TestRuntime_ExecInContainer_RejectsEmptyCommand(t *testing.T) {
41 r := &Runtime{}
42
43 tests := []struct {
44 name string
45 cmd []string
46 }{
47 {name: "nil", cmd: nil},
48 {name: "empty slice", cmd: []string{}},
49 }
50
51 for _, tt := range tests {
52 t.Run(tt.name, func(t *testing.T) {
53 result, err := r.ExecInContainer(context.Background(), "abc123", tt.cmd)
54 require.Error(t, err)
55 assert.Nil(t, result)
56 })
57 }
58}
59
60func frameDockerStream(streamID byte, payload []byte) []byte {
61 frame := make([]byte, 8+len(payload))
62 frame[0] = streamID
63 binary.BigEndian.PutUint32(frame[4:8], uint32(len(payload)))
64 copy(frame[8:], payload)
65 return frame
66}
67
68func TestExtractFileFromTarMatchesFullCleanPath(t *testing.T) {
69 var buf bytes.Buffer
70 tw := tar.NewWriter(&buf)
71 require.NoError(t, writeTarFile(tw, "other/.env", "wrong"))
72 require.NoError(t, writeTarFile(tw, "app/.env", "right"))
73 require.NoError(t, tw.Close())
74
75 rc, err := extractFileFromTar(io.NopCloser(bytes.NewReader(buf.Bytes())), "/app/.env")
76 require.NoError(t, err)
77 defer rc.Close()
78
79 data, err := io.ReadAll(rc)
80 require.NoError(t, err)
81 assert.Equal(t, "right", string(data))
82}
83
84func TestExtractFileFromTarMatchesBasenameForCopiedFile(t *testing.T) {
85 var buf bytes.Buffer
86 tw := tar.NewWriter(&buf)
87 require.NoError(t, writeTarFile(tw, "gordon-backup-test.bak", "backup data"))
88 require.NoError(t, tw.Close())
89
90 rc, err := extractFileFromTar(io.NopCloser(bytes.NewReader(buf.Bytes())), "/tmp/gordon-backup-test.bak")
91 require.NoError(t, err)
92 defer rc.Close()
93
94 data, err := io.ReadAll(rc)
95 require.NoError(t, err)
96 assert.Equal(t, "backup data", string(data))
97}
98
99func TestExtractFileFromTarPrefersExactPathOverBasename(t *testing.T) {
100 var buf bytes.Buffer
101 tw := tar.NewWriter(&buf)
102 require.NoError(t, writeTarFile(tw, "gordon-backup-test.bak", "wrong"))
103 require.NoError(t, writeTarFile(tw, "tmp/gordon-backup-test.bak", "right"))
104 require.NoError(t, tw.Close())
105
106 rc, err := extractFileFromTar(io.NopCloser(bytes.NewReader(buf.Bytes())), "/tmp/gordon-backup-test.bak")
107 require.NoError(t, err)
108 defer rc.Close()
109
110 data, err := io.ReadAll(rc)
111 require.NoError(t, err)
112 assert.Equal(t, "right", string(data))
113}
114
115func TestExtractFileFromTarRejectsAmbiguousBasenameFallback(t *testing.T) {
116 var buf bytes.Buffer
117 tw := tar.NewWriter(&buf)
118 require.NoError(t, writeTarFile(tw, "gordon-backup-test.bak", "first"))
119 require.NoError(t, writeTarFile(tw, "gordon-backup-test.bak", "second"))
120 require.NoError(t, tw.Close())
121
122 rc, err := extractFileFromTar(io.NopCloser(bytes.NewReader(buf.Bytes())), "/tmp/gordon-backup-test.bak")
123 require.Error(t, err)
124 assert.Contains(t, err.Error(), "ambiguous")
125 assert.Nil(t, rc)
126}
127
128func TestExtractFileFromTarDoesNotMatchBasenameInNestedDirectory(t *testing.T) {
129 var buf bytes.Buffer
130 tw := tar.NewWriter(&buf)
131 require.NoError(t, writeTarFile(tw, "other/gordon-backup-test.bak", "wrong"))
132 require.NoError(t, tw.Close())
133
134 rc, err := extractFileFromTar(io.NopCloser(bytes.NewReader(buf.Bytes())), "/tmp/gordon-backup-test.bak")
135 require.Error(t, err)
136 assert.Contains(t, err.Error(), "file not found")
137 assert.Nil(t, rc)
138}
139
140func writeTarFile(tw *tar.Writer, name, content string) error {
141 if err := tw.WriteHeader(&tar.Header{Name: name, Typeflag: tar.TypeReg, Size: int64(len(content))}); err != nil {
142 return err
143 }
144 _, err := tw.Write([]byte(content))
145 return err
146}
147
148func TestReadExtractedEnvFileRejectsOversizedContent(t *testing.T) {
149 data, err := readExtractedEnvFile(io.NopCloser(strings.NewReader(strings.Repeat("a", maxExtractedEnvFileSize+1))))
150 require.Error(t, err)
151 assert.Nil(t, data)
152 assert.Contains(t, err.Error(), "exceeds")
153}