package main import ( "bytes" "crypto/sha256" "encoding/hex" "os" "path/filepath" "sync" "testing" "github.com/stretchr/testify/require" ) func TestImagesArePrivateSharedAndWrittenOnce(t *testing.T) { root, err := openAgent(filepath.Join(t.TempDir(), "agent")) require.NoError(t, err) child := &Agent{ dir: filepath.Join(root.dir, "children", "House"), config: AgentConfig{Parent: root.config.Name}, } require.Equal(t, root.Images().dir, child.Images().dir) data := imageTestPNG(t, 3, 2) sum := sha256.Sum256(data) expected := hex.EncodeToString(sum[:]) + ".png" store := root.Images() name, err := store.Save(data) require.NoError(t, err) require.Equal(t, expected, name) info, err := os.Stat(filepath.Join(store.dir, name)) require.NoError(t, err) require.Equal(t, os.FileMode(0o600), info.Mode().Perm()) var workers sync.WaitGroup for range 8 { workers.Go(func() { saved, saveErr := child.Images().Save(data) require.NoError(t, saveErr) require.Equal(t, name, saved) }) } workers.Wait() again, err := os.Stat(filepath.Join(store.dir, name)) require.NoError(t, err) require.True(t, os.SameFile(info, again)) files, err := os.ReadDir(store.dir) require.NoError(t, err) require.Len(t, files, 1) actual, err := store.Read(name) require.NoError(t, err) require.Equal(t, data, actual) } func TestImageStoreRejectsLargeUnsupportedAndUnsafeImages(t *testing.T) { store := &ImageStore{dir: filepath.Join(t.TempDir(), imageDirName)} _, err := store.Save(bytes.Repeat([]byte{0}, imageMaxBytes+1)) require.ErrorContains(t, err, "exceeds 20 MB") _, err = store.Save([]byte("not an image")) require.ErrorContains(t, err, "JPEG, PNG, or WebP") _, err = store.Read("../secret.png") require.ErrorContains(t, err, "invalid image name") _, err = os.Stat(store.dir) require.ErrorIs(t, err, os.ErrNotExist) } func TestBackupKeepsImagesAndTheirModes(t *testing.T) { dir := t.TempDir() store := &ImageStore{dir: filepath.Join(dir, imageDirName)} data := imageTestPNG(t, 2, 2) name, err := store.Save(data) require.NoError(t, err) require.NoError(t, os.WriteFile(filepath.Join(store.dir, ".image-incomplete"), []byte("partial"), 0o600)) stage := t.TempDir() archive := filepath.Join(t.TempDir(), "images.tar.gz") require.NoError(t, backupCopy(t.Context(), dir, stage, archive)) _, err = backupArchive(t.Context(), stage, archive) require.NoError(t, err) restored := t.TempDir() require.NoError(t, backupRestore(t.Context(), archive, restored)) actual, err := (&ImageStore{dir: filepath.Join(restored, imageDirName)}).Read(name) require.NoError(t, err) require.Equal(t, data, actual) info, err := os.Stat(filepath.Join(restored, imageDirName, name)) require.NoError(t, err) require.Equal(t, os.FileMode(0o600), info.Mode().Perm()) _, err = os.Stat(filepath.Join(restored, imageDirName, ".image-incomplete")) require.ErrorIs(t, err, os.ErrNotExist) } func TestImageMarkersKeepCaptionAndOnlySafeNames(t *testing.T) { name := "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.png" text := imageText([]string{name}, "Read the label.") require.Equal(t, "[image "+name+"]\nRead the label.", text) require.Equal(t, []string{name}, imageFiles(text+"\n[image ../secret.png]\ntext [image "+name+"]")) }