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