d92349ca6e
Vision needs a second model this box does not have, so the shipped half is the part that works without one: an image arrives, is sniffed, is stored content-addressed, and is prepared for inference. The describing half is written and tested against a fake server, and refuses any endpoint that is not on this box. internal/media is the intake all three senses share — hearing and speaker recognition store their audio in the same place under the same retention. Blobs stay out of the sqlite store; only the derived text becomes a note, and only when the caller asks. Retention is enforced by an hourly prune loop rather than by a comment. The plan's RemoteProvider step is refused: no cloud model, inference stays on the box, and vision.NewLocal validates that at construction.
215 lines
5.8 KiB
Go
215 lines
5.8 KiB
Go
package media
|
|
|
|
import (
|
|
"errors"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func testStore(t *testing.T) *Store {
|
|
t.Helper()
|
|
s, err := Open(t.TempDir(), 0, 0)
|
|
if err != nil {
|
|
t.Fatalf("open: %v", err)
|
|
}
|
|
return s
|
|
}
|
|
|
|
func TestPutAndRead(t *testing.T) {
|
|
s := testStore(t)
|
|
b, err := s.Put(KindImage, "image/png", "web:upload", []byte("pretend png"))
|
|
if err != nil {
|
|
t.Fatalf("put: %v", err)
|
|
}
|
|
if len(b.ID) != 64 {
|
|
t.Fatalf("id is not a sha256 hex digest: %q", b.ID)
|
|
}
|
|
if b.Size != int64(len("pretend png")) {
|
|
t.Errorf("size = %d", b.Size)
|
|
}
|
|
if !strings.HasSuffix(b.Path, ".png") {
|
|
t.Errorf("extension not taken from mime: %s", b.Path)
|
|
}
|
|
got, data, err := s.Read(b.ID)
|
|
if err != nil {
|
|
t.Fatalf("read: %v", err)
|
|
}
|
|
if string(data) != "pretend png" {
|
|
t.Errorf("data = %q", data)
|
|
}
|
|
if got.Source != "web:upload" || got.Kind != KindImage {
|
|
t.Errorf("metadata not round-tripped: %+v", got)
|
|
}
|
|
}
|
|
|
|
// The same bytes twice must be one file, and must NOT get a fresh creation
|
|
// time — otherwise re-sending a photo keeps it alive past retention forever.
|
|
func TestPutIsIdempotentAndKeepsFirstSeenTime(t *testing.T) {
|
|
s := testStore(t)
|
|
base := time.Date(2026, 8, 1, 12, 0, 0, 0, time.UTC)
|
|
s.now = func() time.Time { return base }
|
|
|
|
first, err := s.Put(KindAudio, "audio/wav", "capture:meeting", []byte("pcm"))
|
|
if err != nil {
|
|
t.Fatalf("put: %v", err)
|
|
}
|
|
s.now = func() time.Time { return base.Add(72 * time.Hour) }
|
|
second, err := s.Put(KindAudio, "audio/wav", "capture:meeting", []byte("pcm"))
|
|
if err != nil {
|
|
t.Fatalf("re-put: %v", err)
|
|
}
|
|
if first.ID != second.ID {
|
|
t.Fatalf("same bytes produced two ids")
|
|
}
|
|
if !second.Created.Equal(base) {
|
|
t.Errorf("re-put moved created time to %v, want %v", second.Created, base)
|
|
}
|
|
list, err := s.List(KindAudio)
|
|
if err != nil {
|
|
t.Fatalf("list: %v", err)
|
|
}
|
|
if len(list) != 1 {
|
|
t.Errorf("got %d blobs, want 1", len(list))
|
|
}
|
|
}
|
|
|
|
func TestPutRejects(t *testing.T) {
|
|
s, err := Open(t.TempDir(), 8, 0)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := s.Put(KindImage, "image/png", "x", nil); !errors.Is(err, ErrEmpty) {
|
|
t.Errorf("empty payload: %v", err)
|
|
}
|
|
if _, err := s.Put("video", "video/mp4", "x", []byte("ab")); !errors.Is(err, ErrBadKind) {
|
|
t.Errorf("bad kind: %v", err)
|
|
}
|
|
if _, err := s.Put(KindImage, "image/png", "x", []byte("way too many bytes")); !errors.Is(err, ErrTooLarge) {
|
|
t.Errorf("over cap: %v", err)
|
|
}
|
|
}
|
|
|
|
// A caller-supplied id becomes a path, so a traversal attempt must be refused
|
|
// before it touches the filesystem rather than escaping the store root.
|
|
func TestMalformedIDIsRefused(t *testing.T) {
|
|
s := testStore(t)
|
|
for _, id := range []string{"", "../../etc/passwd", strings.Repeat("z", 64), strings.Repeat("a", 63)} {
|
|
if _, err := s.Get(id); !errors.Is(err, ErrBadID) && !errors.Is(err, ErrNotFound) {
|
|
t.Errorf("Get(%q) = %v, want a refusal", id, err)
|
|
}
|
|
if _, _, err := s.Read(id); err == nil {
|
|
t.Errorf("Read(%q) succeeded", id)
|
|
}
|
|
if err := s.Delete(id); err == nil && id != "" {
|
|
// Delete of a well-formed but absent id is fine; these are not
|
|
// well-formed.
|
|
t.Errorf("Delete(%q) succeeded", id)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestGetMissingIsNotFound(t *testing.T) {
|
|
s := testStore(t)
|
|
if _, err := s.Get(strings.Repeat("a", 64)); !errors.Is(err, ErrNotFound) {
|
|
t.Errorf("got %v, want ErrNotFound", err)
|
|
}
|
|
}
|
|
|
|
func TestPruneEnforcesRetention(t *testing.T) {
|
|
s, err := Open(t.TempDir(), 0, 48*time.Hour)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
now := time.Date(2026, 8, 1, 0, 0, 0, 0, time.UTC)
|
|
|
|
s.now = func() time.Time { return now.Add(-96 * time.Hour) }
|
|
old, _ := s.Put(KindAudio, "audio/wav", "capture:meeting", []byte("old meeting"))
|
|
s.now = func() time.Time { return now.Add(-1 * time.Hour) }
|
|
fresh, _ := s.Put(KindImage, "image/png", "telegram", []byte("recent photo"))
|
|
|
|
s.now = func() time.Time { return now }
|
|
n, err := s.Prune()
|
|
if err != nil {
|
|
t.Fatalf("prune: %v", err)
|
|
}
|
|
if n != 1 {
|
|
t.Errorf("pruned %d, want 1", n)
|
|
}
|
|
if _, err := s.Get(old.ID); !errors.Is(err, ErrNotFound) {
|
|
t.Errorf("stale blob survived prune: %v", err)
|
|
}
|
|
if _, err := s.Get(fresh.ID); err != nil {
|
|
t.Errorf("fresh blob was pruned: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestListIsNewestFirstAcrossKinds(t *testing.T) {
|
|
s := testStore(t)
|
|
base := time.Date(2026, 8, 1, 0, 0, 0, 0, time.UTC)
|
|
s.now = func() time.Time { return base }
|
|
_, _ = s.Put(KindImage, "image/png", "telegram", []byte("one"))
|
|
s.now = func() time.Time { return base.Add(time.Hour) }
|
|
newest, _ := s.Put(KindAudio, "audio/wav", "capture:meeting", []byte("two"))
|
|
|
|
all, err := s.List("")
|
|
if err != nil {
|
|
t.Fatalf("list: %v", err)
|
|
}
|
|
if len(all) != 2 {
|
|
t.Fatalf("got %d, want 2", len(all))
|
|
}
|
|
if all[0].ID != newest.ID {
|
|
t.Errorf("list is not newest-first")
|
|
}
|
|
}
|
|
|
|
// Recordings of people are 0700/0600 and nothing else.
|
|
func TestPermissionsAreOwnerOnly(t *testing.T) {
|
|
dir := t.TempDir()
|
|
s, err := Open(filepath.Join(dir, "blobs"), 0, 0)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
b, err := s.Put(KindAudio, "audio/wav", "capture:meeting", []byte("pcm"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
di, err := os.Stat(s.Dir())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if di.Mode().Perm() != 0o700 {
|
|
t.Errorf("store dir mode = %o, want 700", di.Mode().Perm())
|
|
}
|
|
fi, err := os.Stat(b.Path)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if fi.Mode().Perm() != 0o600 {
|
|
t.Errorf("blob mode = %o, want 600", fi.Mode().Perm())
|
|
}
|
|
}
|
|
|
|
func TestDeleteRemovesBytesAndSidecar(t *testing.T) {
|
|
s := testStore(t)
|
|
b, _ := s.Put(KindImage, "image/png", "telegram", []byte("bytes"))
|
|
if err := s.Delete(b.ID); err != nil {
|
|
t.Fatalf("delete: %v", err)
|
|
}
|
|
if _, err := os.Stat(b.Path); !os.IsNotExist(err) {
|
|
t.Errorf("bytes survived delete")
|
|
}
|
|
if _, err := s.Get(b.ID); !errors.Is(err, ErrNotFound) {
|
|
t.Errorf("sidecar survived delete: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestOpenRejectsEmptyDir(t *testing.T) {
|
|
if _, err := Open(" ", 0, 0); err == nil {
|
|
t.Error("empty dir accepted")
|
|
}
|
|
}
|