mirror of
https://github.com/hanzoai/vfs.git
synced 2026-10-30 18:18:21 +00:00
Modules declare go 1.27.1 and the build images follow. go fix applied the modernizers; omitzero is not applied, since it changes what the JSON says. Co-authored-by: Hanzo Dev <[email protected]>
117 lines
3.0 KiB
Go
117 lines
3.0 KiB
Go
package vfs_test
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/luxfi/age"
|
|
|
|
"github.com/hanzoai/vfs"
|
|
"github.com/hanzoai/vfs/pkg/backend"
|
|
_ "github.com/hanzoai/vfs/pkg/backend/file"
|
|
)
|
|
|
|
func newRoundTripVFS(t *testing.T) *vfs.VFS {
|
|
t.Helper()
|
|
dir := t.TempDir()
|
|
be, err := backend.Open(context.Background(), "file://"+dir)
|
|
if err != nil {
|
|
t.Fatalf("backend.Open: %v", err)
|
|
}
|
|
t.Cleanup(func() { _ = be.Close() })
|
|
|
|
id, err := age.GenerateX25519Identity()
|
|
if err != nil {
|
|
t.Fatalf("age.GenerateX25519Identity: %v", err)
|
|
}
|
|
c, err := vfs.NewCrypto([]age.Recipient{id.Recipient()}, []age.Identity{id})
|
|
if err != nil {
|
|
t.Fatalf("NewCrypto: %v", err)
|
|
}
|
|
|
|
v, err := vfs.New(vfs.Config{
|
|
Backend: be,
|
|
Crypto: c,
|
|
CacheMax: 0, // unbounded for the test
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("vfs.New: %v", err)
|
|
}
|
|
return v
|
|
}
|
|
|
|
func TestRoundTrip(t *testing.T) {
|
|
v := newRoundTripVFS(t)
|
|
ctx := context.Background()
|
|
|
|
plaintext := []byte("hello, vfs world — this is a small block")
|
|
id, err := v.PutBlock(ctx, plaintext)
|
|
if err != nil {
|
|
t.Fatalf("PutBlock: %v", err)
|
|
}
|
|
if !strings.HasPrefix(string(id), "") || len(id) != 64 {
|
|
t.Fatalf("BlockID looks wrong: %q (len %d, want 64)", id, len(id))
|
|
}
|
|
|
|
got, err := v.GetBlock(ctx, id)
|
|
if err != nil {
|
|
t.Fatalf("GetBlock: %v", err)
|
|
}
|
|
|
|
// Decrypted block is zero-padded to BlockSize. Logical content is at the start.
|
|
if len(got) != vfs.BlockSize {
|
|
t.Fatalf("GetBlock returned %d bytes, want BlockSize=%d", len(got), vfs.BlockSize)
|
|
}
|
|
if string(got[:len(plaintext)]) != string(plaintext) {
|
|
t.Fatalf("plaintext mismatch:\n got=%q\n want=%q", got[:len(plaintext)], plaintext)
|
|
}
|
|
for i := len(plaintext); i < vfs.BlockSize; i++ {
|
|
if got[i] != 0 {
|
|
t.Fatalf("byte %d should be zero pad, got 0x%x", i, got[i])
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestRoundTripDeduplicates(t *testing.T) {
|
|
// Same plaintext + same recipient + same identity → identical ciphertext is NOT
|
|
// guaranteed by age (it includes a random ephemeral key). What we DO guarantee
|
|
// is that repeated PutBlock calls of the same plaintext return DIFFERENT block
|
|
// IDs but each is independently retrievable. (Cross-fleet dedup is a separate
|
|
// design with deterministic key derivation; that lands in 0.2.0+.)
|
|
v := newRoundTripVFS(t)
|
|
ctx := context.Background()
|
|
|
|
id1, err := v.PutBlock(ctx, []byte("identical"))
|
|
if err != nil {
|
|
t.Fatalf("PutBlock 1: %v", err)
|
|
}
|
|
id2, err := v.PutBlock(ctx, []byte("identical"))
|
|
if err != nil {
|
|
t.Fatalf("PutBlock 2: %v", err)
|
|
}
|
|
// Both must round-trip even if the IDs differ.
|
|
for _, id := range []vfs.BlockID{id1, id2} {
|
|
if _, err := v.GetBlock(ctx, id); err != nil {
|
|
t.Fatalf("GetBlock(%s): %v", id, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestStats(t *testing.T) {
|
|
v := newRoundTripVFS(t)
|
|
ctx := context.Background()
|
|
for i := range 3 {
|
|
if _, err := v.PutBlock(ctx, []byte{byte(i)}); err != nil {
|
|
t.Fatalf("PutBlock: %v", err)
|
|
}
|
|
}
|
|
s := v.Stats()
|
|
if s.CacheBlocks != 3 {
|
|
t.Fatalf("CacheBlocks = %d, want 3", s.CacheBlocks)
|
|
}
|
|
if s.Backend == "" {
|
|
t.Fatal("Stats.Backend empty")
|
|
}
|
|
}
|