Adopt the standard tooling: a Makefile of thin shims over a full scripts-to-rule-them-all `script/` set, the canonical `.golangci.yml` vendored byte-identical from `sneak/prompts`, docker-only linting via `Dockerfile.lint`, a `Dockerfile` and Gitea workflow that gate every push, and prettier/editorconfig/dockerignore config. `make build` pointed at a `cmd/dcfinfo` that is not in the tree and could never have succeeded; this repo is a library, so `build` is now the compile check over every package. Clear the 57 findings the canonical linter config reports on `pkg/dcf`. Two were real: `findDCFMountPoints` checked a never-assigned `erro` instead of the error from `findAllMountPoints`, discarding it, and `privatePath` was computed twice so the first computation was dead. Mountpoint selection otherwise behaves exactly as before; the defects that survive are filed as issue #6, not fixed here. Rename `DCFStore` to `Store` and `DCFObject` to `Object` (with its `DCFStoreRoot` field to `StoreRoot`), which revive's stutter rule requires and which the `fs.FS` rework in issue #4 will build on. Replace the placeholder test with tests over the exported surface. The filesystem walk stays uncovered: it is reachable only through `GetDCFStores`, which needs real mounted media. README keeps its content, reorganised into the required sections and gaining Entrypoints. (closes #1)
178 lines
3.9 KiB
Go
178 lines
3.9 KiB
Go
package dcf_test
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"git.eeqj.de/sneak/dcf/pkg/dcf"
|
|
)
|
|
|
|
// newStore builds a store with two images and one video of known sizes,
|
|
// so the count and size accessors have something deterministic to
|
|
// report.
|
|
func newStore(root string) *dcf.Store {
|
|
return &dcf.Store{
|
|
RootDirectory: root,
|
|
Images: []*dcf.Image{
|
|
{Object: dcf.Object{
|
|
StoreRoot: root,
|
|
Path: "DCIM/100MSDCF/DSC00001.ARW",
|
|
Size: 2000,
|
|
Extension: ".ARW",
|
|
}},
|
|
{Object: dcf.Object{
|
|
StoreRoot: root,
|
|
Path: "DCIM/100MSDCF/DSC00002.JPG",
|
|
Size: 500,
|
|
Extension: ".JPG",
|
|
}},
|
|
},
|
|
Videos: []*dcf.Video{
|
|
{Object: dcf.Object{
|
|
StoreRoot: root,
|
|
Path: "PRIVATE/M4ROOT/CLIP/C0001.MP4",
|
|
Size: 9000,
|
|
Extension: ".MP4",
|
|
}},
|
|
},
|
|
}
|
|
}
|
|
|
|
func TestObjectFullFilePath(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
object := dcf.Object{
|
|
StoreRoot: filepath.Join("/mnt", "card"),
|
|
Path: filepath.Join("DCIM", "100MSDCF", "DSC00001.ARW"),
|
|
}
|
|
|
|
want := filepath.Join("/mnt", "card", "DCIM", "100MSDCF", "DSC00001.ARW")
|
|
if got := object.FullFilePath(); got != want {
|
|
t.Fatalf("FullFilePath() = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestStoreCounts(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
store := newStore("/mnt/card")
|
|
|
|
if got := store.ImagesCount(); got != 2 {
|
|
t.Errorf("ImagesCount() = %d, want 2", got)
|
|
}
|
|
|
|
if got := store.VideosCount(); got != 1 {
|
|
t.Errorf("VideosCount() = %d, want 1", got)
|
|
}
|
|
}
|
|
|
|
func TestStoreSizes(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
store := newStore("/mnt/card")
|
|
|
|
if got := store.TotalImageSize(); got != 2500 {
|
|
t.Errorf("TotalImageSize() = %d, want 2500", got)
|
|
}
|
|
|
|
if got := store.TotalVideoSize(); got != 9000 {
|
|
t.Errorf("TotalVideoSize() = %d, want 9000", got)
|
|
}
|
|
|
|
if got := store.TotalSize(); got != 11500 {
|
|
t.Errorf("TotalSize() = %d, want 11500", got)
|
|
}
|
|
}
|
|
|
|
func TestEmptyStoreSizesAreZero(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
store := dcf.Store{RootDirectory: "/mnt/empty"}
|
|
if got := store.TotalSize(); got != 0 {
|
|
t.Fatalf("TotalSize() = %d, want 0", got)
|
|
}
|
|
}
|
|
|
|
func TestStoreStringReportsHumanizedTotal(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
got := newStore("/mnt/card").String()
|
|
for _, want := range []string{"/mnt/card", "Images: 2", "Videos: 1", "12 kB"} {
|
|
if !strings.Contains(got, want) {
|
|
t.Errorf("String() = %q, want it to contain %q", got, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestImageAndVideoString(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
image := dcf.Image{Object: dcf.Object{
|
|
Path: "DCIM/100MSDCF/DSC00001.ARW",
|
|
Size: 2000,
|
|
Extension: ".ARW",
|
|
}}
|
|
|
|
want := "Image{Path: DCIM/100MSDCF/DSC00001.ARW, Size: 2000, " +
|
|
"Extension: .ARW}"
|
|
if got := image.String(); got != want {
|
|
t.Errorf("Image.String() = %q, want %q", got, want)
|
|
}
|
|
|
|
video := dcf.Video{Object: dcf.Object{
|
|
Path: "PRIVATE/M4ROOT/CLIP/C0001.MP4",
|
|
Size: 9000,
|
|
Extension: ".MP4",
|
|
}}
|
|
|
|
want = "Video{Path: PRIVATE/M4ROOT/CLIP/C0001.MP4, Size: 9000, " +
|
|
"Extension: .MP4}"
|
|
if got := video.String(); got != want {
|
|
t.Errorf("Video.String() = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestObjectHashMatchesSHA256OfFile(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
root := t.TempDir()
|
|
contents := []byte("DSC00001.ARW contents")
|
|
|
|
err := os.MkdirAll(filepath.Join(root, "DCIM"), 0o750)
|
|
if err != nil {
|
|
t.Fatalf("MkdirAll: %v", err)
|
|
}
|
|
|
|
err = os.WriteFile(filepath.Join(root, "DCIM", "a.arw"), contents, 0o600)
|
|
if err != nil {
|
|
t.Fatalf("WriteFile: %v", err)
|
|
}
|
|
|
|
object := dcf.Object{StoreRoot: root, Path: filepath.Join("DCIM", "a.arw")}
|
|
|
|
got, err := object.Hash()
|
|
if err != nil {
|
|
t.Fatalf("Hash() returned error: %v", err)
|
|
}
|
|
|
|
sum := sha256.Sum256(contents)
|
|
if want := hex.EncodeToString(sum[:]); got != want {
|
|
t.Fatalf("Hash() = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestObjectHashOnMissingFileReturnsError(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
object := dcf.Object{StoreRoot: t.TempDir(), Path: "absent.jpg"}
|
|
|
|
_, err := object.Hash()
|
|
if err == nil {
|
|
t.Fatal("Hash() on a missing file returned no error")
|
|
}
|
|
}
|