@@ -20,6 +20,7 @@ import (
2020 "bytes"
2121 "context"
2222 "fmt"
23+ "io"
2324 "os"
2425 "path/filepath"
2526 "strings"
@@ -280,3 +281,114 @@ services:
280281 normalizeSpaces(actualOutput),
281282 "\nExpected:\n%s\nGot:\n%s", expected, actualOutput)
282283}
284+
285+ func TestConfirmRemoteIncludes(t *testing.T) {
286+ ctrl := gomock.NewController(t)
287+ defer ctrl.Finish()
288+ cli := mocks.NewMockCli(ctrl)
289+
290+ tests := []struct {
291+ name string
292+ opts buildOptions
293+ assumeYes bool
294+ userInput string
295+ wantErr bool
296+ errMessage string
297+ wantPrompt bool
298+ wantOutput string
299+ }{
300+ {
301+ name: "no remote includes",
302+ opts: buildOptions{
303+ ProjectOptions: &ProjectOptions{
304+ ConfigPaths: []string{
305+ "docker-compose.yaml",
306+ "./local/path/compose.yaml",
307+ },
308+ },
309+ },
310+ assumeYes: false,
311+ wantErr: false,
312+ wantPrompt: false,
313+ },
314+ {
315+ name: "assume yes with remote includes",
316+ opts: buildOptions{
317+ ProjectOptions: &ProjectOptions{
318+ ConfigPaths: []string{
319+ "oci://registry.example.com/stack:latest",
320+ "git://github.com/user/repo.git",
321+ },
322+ },
323+ },
324+ assumeYes: true,
325+ wantErr: false,
326+ wantPrompt: false,
327+ },
328+ {
329+ name: "user confirms remote includes",
330+ opts: buildOptions{
331+ ProjectOptions: &ProjectOptions{
332+ ConfigPaths: []string{
333+ "oci://registry.example.com/stack:latest",
334+ "git://github.com/user/repo.git",
335+ },
336+ },
337+ },
338+ assumeYes: false,
339+ userInput: "y\n",
340+ wantErr: false,
341+ wantPrompt: true,
342+ wantOutput: "\nWarning: This Compose project includes files from remote sources:\n" +
343+ " - oci://registry.example.com/stack:latest\n" +
344+ " - git://github.com/user/repo.git\n" +
345+ "\nRemote includes could potentially be malicious. Make sure you trust the source.\n" +
346+ "Do you want to continue? [y/N]: ",
347+ },
348+ {
349+ name: "user rejects remote includes",
350+ opts: buildOptions{
351+ ProjectOptions: &ProjectOptions{
352+ ConfigPaths: []string{
353+ "oci://registry.example.com/stack:latest",
354+ },
355+ },
356+ },
357+ assumeYes: false,
358+ userInput: "n\n",
359+ wantErr: true,
360+ errMessage: "operation cancelled by user",
361+ wantPrompt: true,
362+ wantOutput: "\nWarning: This Compose project includes files from remote sources:\n" +
363+ " - oci://registry.example.com/stack:latest\n" +
364+ "\nRemote includes could potentially be malicious. Make sure you trust the source.\n" +
365+ "Do you want to continue? [y/N]: ",
366+ },
367+ }
368+
369+ buf := new(bytes.Buffer)
370+ for _, tt := range tests {
371+ t.Run(tt.name, func(t *testing.T) {
372+ cli.EXPECT().Out().Return(streams.NewOut(buf)).AnyTimes()
373+
374+ if tt.wantPrompt {
375+ inbuf := io.NopCloser(bytes.NewBufferString(tt.userInput))
376+ cli.EXPECT().In().Return(streams.NewIn(inbuf)).AnyTimes()
377+ }
378+
379+ err := confirmRemoteIncludes(cli, tt.opts, tt.assumeYes)
380+
381+ if tt.wantErr {
382+ require.Error(t, err)
383+ require.Equal(t, tt.errMessage, err.Error())
384+ } else {
385+ require.NoError(t, err)
386+ }
387+
388+ if tt.wantOutput != "" {
389+ require.Equal(t, tt.wantOutput, buf.String())
390+ }
391+ buf.Reset()
392+ })
393+ }
394+ }
0 commit comments