Files
vfs/block_test.go
Hanzo DevandGitHub 6c3cfe9751 feat: cloud Mount() per HIP-0106 (#1)
* feat: cloud Mount() per HIP-0106

Adds pkg/vfs.Mount(*zip.App, cloud.Deps) error so the unified Hanzo
Cloud binary can blank-import and register VFS alongside every
other Hanzo subsystem.

VFS has no public HTTP surface today — it's an S3-backed virtual
block filesystem driven through FUSE/CLI/sidecar. Mount() exposes
liveness/readiness probes plus a minimal block-CRUD HTTP API so
co-resident subsystems can talk to VFS without falling back to the
CLI:

- GET    /v1/vfs/health       — always 200
- GET    /v1/vfs/readyz       — 200 only when an instance is
                                attached via vfs.SetInstance
- PUT    /v1/vfs/blocks       — payload upload
- GET    /v1/vfs/blocks/:id   — payload download
- DELETE /v1/vfs/blocks/:id   — backend delete
- GET    /v1/vfs/stats        — cache/backend counters

cmd/vfsd is a HIP-0106 thin shim that wires backend + age keys from
env (VFSD_BACKEND, VFSD_AGE_KEY) and listens on cloud.Config.ListenAddr.
The existing cmd/vfs CLI (put/get/stats/mount) is untouched.

init() registers with cloud.Register("vfs", 20, …). schema/vfs.zap
adds the minimal Health + Block ZAP interface; full multi-block
File surface lands in follow-up PRs.

Tests:
- pkg/vfs: TestMount_HealthReadyz + TestMount_PutGetRoundtrip green
- existing TestRoundTrip + TestStats unchanged

* refactor: collapse pkg/vfs/ → root for clean import path

Subsystem code lives in package vfs at repo root. Importers now use
`github.com/hanzoai/vfs` (no /pkg/vfs/ nesting). Standalone shim moved
to cmd/vfs/ where applicable.

Per Hanzo Go-stdlib pattern: repo IS the package.

* refactor: flatten import path — github.com/hanzoai/cloud (drop /pkg/cloud)
2026-05-18 23:37:45 -07:00

71 lines
1.6 KiB
Go

package vfs
import (
"strings"
"testing"
)
func TestHashBlockDeterministic(t *testing.T) {
a := HashBlock([]byte("hello"))
b := HashBlock([]byte("hello"))
if a != b {
t.Fatalf("hash not deterministic: %s != %s", a, b)
}
c := HashBlock([]byte("hello!"))
if a == c {
t.Fatalf("hash should differ for different inputs: a=%s c=%s", a, c)
}
}
func TestBlockIDPath(t *testing.T) {
id := HashBlock([]byte("test"))
p := id.Path()
if !strings.HasPrefix(p, "blocks/") {
t.Fatalf("expected blocks/ prefix, got %q", p)
}
if !strings.HasSuffix(p, ".zap.age") {
t.Fatalf("expected .zap.age suffix, got %q", p)
}
if !strings.Contains(p, string(id[:2])+"/") {
t.Fatalf("expected 2-hex shard prefix containing %q, got %q", string(id[:2]), p)
}
}
func TestVerifyMatch(t *testing.T) {
ct := []byte("ciphertext bytes")
id := HashBlock(ct)
if err := id.Verify(ct); err != nil {
t.Fatalf("verify same bytes: %v", err)
}
if err := id.Verify([]byte("different")); err == nil {
t.Fatal("verify should fail on tampered bytes")
}
}
func TestPad(t *testing.T) {
short := Pad([]byte("abc"))
if len(short) != BlockSize {
t.Fatalf("Pad short: got %d want %d", len(short), BlockSize)
}
for i := 3; i < BlockSize; i++ {
if short[i] != 0 {
t.Fatalf("Pad short: byte %d should be zero, got 0x%x", i, short[i])
}
}
exact := make([]byte, BlockSize)
for i := range exact {
exact[i] = byte(i)
}
out := Pad(exact)
if len(out) != BlockSize {
t.Fatalf("Pad exact: got %d want %d", len(out), BlockSize)
}
long := make([]byte, BlockSize+100)
out = Pad(long)
if len(out) != BlockSize {
t.Fatalf("Pad long: got %d want %d", len(out), BlockSize)
}
}