git/intake/main
internal/cli/dryrun_test.go
132 lines4 KB last change raw · history
package cli

import (
	"bytes"
	"context"
	"intake/internal/audio"
	"intake/internal/plan"
	"intake/internal/scan"
	"os"
	"path/filepath"
	"strings"
	"testing"

	"intake/internal/artist"
	"intake/internal/config"
)

func TestDryRunDoesNotWriteAliases(t *testing.T) {
	root := t.TempDir()
	var errBuf bytes.Buffer
	a := &App{
		Cfg:    config.Config{Root: root},
		DryRun: true,
		Err:    &errBuf,
		Out:    &bytes.Buffer{},
	}
	ix := artist.NewIndex()
	a.rememberAlias(ix, "Some Credit", "some-folder")

	path := artist.AliasPath(root)
	if _, err := os.Stat(path); !os.IsNotExist(err) {
		t.Fatalf("a dry run wrote %s", path)
	}
	if got := ix.Alias["Some Credit"]; got != "some-folder" {
		t.Errorf("in-memory alias = %q, want %q", got, "some-folder")
	}
}

func TestAliasesAreWrittenNormally(t *testing.T) {
	root := t.TempDir()
	a := &App{
		Cfg: config.Config{Root: root},
		Err: &bytes.Buffer{},
		Out: &bytes.Buffer{},
	}
	ix := artist.NewIndex()
	a.rememberAlias(ix, "Some Credit", "some-folder")

	if _, err := os.Stat(artist.AliasPath(root)); err != nil {
		t.Fatalf("aliases were not saved: %v", err)
	}
	reloaded := artist.NewIndex()
	if err := reloaded.LoadAliases(artist.AliasPath(root)); err != nil {
		t.Fatal(err)
	}
	if got := reloaded.Alias["Some Credit"]; got != "some-folder" {
		t.Errorf("reloaded alias = %q, want %q", got, "some-folder")
	}
	_ = filepath.Join
}

// An album that files and then cannot be recorded is worse than one that never
// files: it lands in the archive untracked, so verify and audit skip it and a
// later run offers to file it again. The journal is proved writable before the
// first byte is copied, and that has to happen before anything is created.
func TestFilingRefusesWhenTheJournalCannotBeWritten(t *testing.T) {
	root := t.TempDir()
	// A file where the state directory needs to be, so creating it fails.
	blocker := filepath.Join(root, "blocked")
	if err := os.WriteFile(blocker, []byte("not a directory"), 0o600); err != nil {
		t.Fatal(err)
	}

	a := &App{
		Cfg: config.Config{Root: root, StateDir: filepath.Join(blocker, "state")},
		Out: &bytes.Buffer{},
		Err: &bytes.Buffer{},
	}
	archive := filepath.Join(root, "archive", "an-artist", "an-album")
	_, err := a.FilePlan(context.Background(), plan.Plan{ArchiveDir: archive}, coverChoice{}, "")
	if err == nil {
		t.Fatal("filing should have been refused")
	}
	if !strings.Contains(err.Error(), "journal") {
		t.Errorf("the refusal should name the journal: %v", err)
	}
	if _, statErr := os.Stat(filepath.Join(root, "archive")); !os.IsNotExist(statErr) {
		t.Error("the archive was created even though the journal could not be written")
	}
}

// --yes is for unattended runs. A name that cannot be worked out used to send it
// into the editor anyway, which waits for somebody who is not there and hangs
// the whole batch on that album forever.
func TestYesRefusesRatherThanOpeningTheEditor(t *testing.T) {
	root := t.TempDir()
	marker := filepath.Join(root, "editor-ran")
	script := filepath.Join(root, "editor.sh")
	if err := os.WriteFile(script, []byte("#!/bin/sh\ntouch "+marker+"\n"), 0o755); err != nil {
		t.Fatal(err)
	}

	cfg := config.Defaults()
	cfg.Root = root
	cfg.StagingDir = "staging"
	cfg.ArchiveDir = "archive"
	cfg.LibraryDir = "library"
	cfg.StateDir = filepath.Join(root, "state")
	cfg.Editor = script

	var out, errBuf bytes.Buffer
	a := &App{Cfg: cfg, Yes: true, Out: &out, Err: &errBuf}

	album := scan.Album{
		Name:   "nameless",
		Dir:    filepath.Join(root, "staging", "nameless"),
		Status: scan.StatusReady,
		// no tags at all, and a folder name with no separator to infer from
		Tracks: []audio.Track{{
			Path: filepath.Join(root, "staging", "nameless", "01 untitled.flac"),
			Ext:  ".flac",
		}},
	}

	if err := a.process(context.Background(), album); err == nil {
		t.Fatal("--yes should have refused an album whose names cannot be worked out")
	}
	if _, statErr := os.Stat(marker); !os.IsNotExist(statErr) {
		t.Error("the editor was opened under --yes, which is what hangs an unattended run")
	}
}