Files
base/network/archive_integration_test.go
zeekayandHanzo Dev ecd36df7c4 the object store is ours, and the protocol is not named after a vendor
The integration test told a developer to run somebody else's server to
exercise our own storage backend. It runs ours, which creates the bucket from
the environment rather than needing a shell into the container afterwards.

The client is imported under the name of the thing it speaks, which is what
the sibling service already does, so a call site reads as the protocol rather
than as whoever wrote the library. No dependency moved: an S3 client talks to
an S3 server, and ours is one.

Comments called the protocol by a vendor's name, and the network document
pointed at a directory that does not exist.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-14 09:11:45 -07:00

103 lines
2.7 KiB
Go

//go:build integration
// Integration tests for the S3 archive backend. Gated behind the
// "integration" build tag so they never run in the standard go test
// cycle. They run against our own S3 server, hanzoai/s3, whose `mini`
// command brings up the whole stack and creates the bucket for us:
//
// docker run --rm -d --name s3-test -p 8333:8333 \
// -e AWS_ACCESS_KEY_ID=admin -e AWS_SECRET_ACCESS_KEY=secret \
// -e S3_BUCKET=base-archive-test \
// ghcr.io/hanzoai/s3 mini -dir=/data
//
// AWS_ENDPOINT_URL=http://127.0.0.1:8333 \
// AWS_ACCESS_KEY_ID=admin AWS_SECRET_ACCESS_KEY=secret \
// go test -tags=integration ./network -run TestS3Integration -v
package network
import (
"context"
"fmt"
"os"
"testing"
"time"
)
func skipWithoutS3(t *testing.T) string {
t.Helper()
if os.Getenv("AWS_ENDPOINT_URL") == "" {
t.Skip("AWS_ENDPOINT_URL unset; start an S3 container per the file header")
}
b := os.Getenv("BASE_ARCHIVE_TEST_BUCKET")
if b == "" {
b = "base-archive-test"
}
return b
}
func TestS3IntegrationRoundTrip(t *testing.T) {
bucket := skipWithoutS3(t)
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
defer cancel()
a, err := NewArchive(ctx, ArchiveConfig{
URL: fmt.Sprintf("s3://%s", bucket),
SegmentTargetBytes: 4096,
FlushInterval: 100 * time.Millisecond,
RetryDeadline: 30 * time.Second,
}, "svc-itest", nil)
if err != nil {
t.Fatalf("NewArchive: %v", err)
}
if a == nil {
t.Fatalf("NewArchive returned nil archive")
}
t.Cleanup(func() { _ = a.Close() })
const n = 1000
for i := uint64(1); i <= n; i++ {
f := newFrame("shard-itest", i, i-1, []byte(fmt.Sprintf("payload-%d", i)))
if err := a.Append(ctx, "shard-itest", i, f.encode()); err != nil {
t.Fatalf("append %d: %v", i, err)
}
}
if err := a.Close(); err != nil {
t.Fatalf("close: %v", err)
}
// Re-open to read back — proves persistence across archive instances.
a2, err := NewArchive(ctx, ArchiveConfig{
URL: fmt.Sprintf("s3://%s", bucket),
}, "svc-itest", nil)
if err != nil {
t.Fatalf("NewArchive reopen: %v", err)
}
t.Cleanup(func() { _ = a2.Close() })
it, err := a2.Range(ctx, "shard-itest", 1, n)
if err != nil {
t.Fatalf("Range: %v", err)
}
var got uint64
for f, err := range it {
if err != nil {
t.Fatalf("iter err: %v", err)
}
got++
if f.Seq != got {
t.Fatalf("seq want %d got %d", got, f.Seq)
}
if err := f.Valid(); err != nil {
t.Fatalf("frame %d invalid: %v", got, err)
}
want := fmt.Sprintf("payload-%d", got)
if string(f.Payload) != want {
t.Fatalf("payload %d: want %q got %q", got, want, string(f.Payload))
}
}
if got != n {
t.Fatalf("want %d frames, got %d", n, got)
}
}