package hash
import (
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
)
// TestSingleFileMatchesSha256sumCutC116 asserts the central parity claim:
// Derive([f], 16) == `sha256sum f | cut -c1-16`. This is what the existing
// shell helper does (HASH=$(sha256sum docker/conformance.Dockerfile | cut -c1-16))
// and the new tool must produce the same key for the same input.
func TestSingleFileMatchesSha256sumCutC116(t *testing.T) {
if _, err := exec.LookPath("sha256sum"); err != nil {
t.Skip("sha256sum not on PATH; skipping shell parity check")
}
path := filepath.Join(t.TempDir(), "sample")
if err := os.WriteFile(path, []byte("hello world\n"), 0o600); err != nil {
t.Fatal(err)
}
out, err := exec.Command("sha256sum", path).Output()
if err != nil {
t.Fatalf("sha256sum: %v", err)
}
want := strings.Fields(string(out))[0][:16]
got, err := Derive([]string{path}, 16)
if err != nil {
t.Fatalf("Derive: %v", err)
}
if got != want {
t.Errorf("Derive=%q want=%q", got, want)
}
}
func TestDirHashIsStable(t *testing.T) {
dir := t.TempDir()
mustWrite := func(rel, body string) {
p := filepath.Join(dir, rel)
if err := os.MkdirAll(filepath.Dir(p), 0o700); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(p, []byte(body), 0o600); err != nil {
t.Fatal(err)
}
}
mustWrite("a/x", "x-body")
mustWrite("b/y/z", "z-body")
h1, err := Derive([]string{dir}, 32)
if err != nil {
t.Fatal(err)
}
h2, err := Derive([]string{dir}, 32)
if err != nil {
t.Fatal(err)
}
if h1 != h2 {
t.Errorf("dir hash not stable: %q vs %q", h1, h2)
}
}
func TestDirHashChangesOnContentChange(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "f")
_ = os.WriteFile(path, []byte("v1"), 0o600)
h1, _ := Derive([]string{dir}, 32)
_ = os.WriteFile(path, []byte("v2"), 0o600)
h2, _ := Derive([]string{dir}, 32)
if h1 == h2 {
t.Error("dir hash unchanged after content change")
}
}
func TestMultiPathCombines(t *testing.T) {
dir := t.TempDir()
a := filepath.Join(dir, "a")
b := filepath.Join(dir, "b")
_ = os.WriteFile(a, []byte("A"), 0o600)
_ = os.WriteFile(b, []byte("B"), 0o600)
ha, _ := Derive([]string{a}, 16)
hab, _ := Derive([]string{a, b}, 16)
hba, _ := Derive([]string{b, a}, 16)
if ha == hab {
t.Error("combining two paths produced same hash as single path")
}
if hab == hba {
t.Error("order of --hash-from paths should matter")
}
}
func TestApplyTemplateHashPlaceholder(t *testing.T) {
got := ApplyTemplate("img/{hash}.tar.zst", "abcd1234", "linux", "amd64", false)
if got != "img/abcd1234.tar.zst" {
t.Errorf("ApplyTemplate placeholder = %q", got)
}
}
func TestApplyTemplateNoPlaceholderAppends(t *testing.T) {
got := ApplyTemplate("img/build.tar.zst", "abcd1234", "linux", "amd64", false)
if got != "img/build-abcd1234.tar.zst" {
t.Errorf("ApplyTemplate append = %q", got)
}
}
func TestApplyTemplateArchSuffix(t *testing.T) {
got := ApplyTemplate("img/build.tar.zst", "abcd", "linux", "amd64", true)
if got != "img/build-abcd-linux-amd64.tar.zst" {
t.Errorf("ApplyTemplate arch = %q", got)
}
}
func TestApplyTemplateArchSuffixSimpleExt(t *testing.T) {
got := ApplyTemplate("bin/cacher", "", "linux", "amd64", true)
if got != "bin/cacher-linux-amd64" {
t.Errorf("ApplyTemplate arch no-ext = %q", got)
}
}
func TestDeriveRejectsEmpty(t *testing.T) {
if _, err := Derive(nil, 16); err == nil {
t.Error("expected error for empty paths")
}
}