test(integration): migrate CreateVolume to assert real predastore persistence

Add StartVolumeDaemonLite, an opt-in DaemonLite variant wired to the
real predastore fixture, and migrate TestCreateVolume_PersistsToRealPredastore
off memory-backed DaemonLite so CreateVolume/DescribeVolumes exercise
viperblock's real S3 backend end to end instead of stopping at input
validation.
This commit is contained in:
joshsiv-mulga
2026-07-20 16:58:09 +10:00
parent b670ebca49
commit 02f4eb46a5
2 changed files with 120 additions and 0 deletions
+71
View File
@@ -0,0 +1,71 @@
//go:build integration
package integration
import (
"context"
"testing"
"github.com/mulgadc/spinifex/spinifex/config"
handlers_ec2_volume "github.com/mulgadc/spinifex/spinifex/handlers/ec2/volume"
"github.com/mulgadc/spinifex/spinifex/objectstore"
testpredastore "github.com/mulgadc/spinifex/spinifex/testutil/predastore"
"github.com/nats-io/nats.go"
"github.com/stretchr/testify/require"
)
// testVolumeBucket is the real predastore bucket StartVolumeDaemonLite's
// VolumeServiceImpl is wired against. Unlike testPredastoreBucket
// (memory-backed, name never resolved — see StartDaemonLite), this bucket
// genuinely exists on the shared predastore fixture: viperblock's S3 backend
// dials it for real, so chunk uploads and config.json persistence are real.
const testVolumeBucket = "integration-test-volumes"
// StartVolumeDaemonLite subscribes a real handlers_ec2_volume.VolumeServiceImpl
// — the same production code a live daemon runs (daemon/daemon_handlers_volume.go)
// — to the ec2.CreateVolume/DeleteVolume/DescribeVolumes subjects, backed by a
// real predastore daemon (testpredastore.Start) rather than the memory-backed
// stores DaemonLite's other resources use.
//
// This is deliberately a separate opt-in wiring rather than part of
// StartDaemonLite: CreateVolume constructs viperblock.New and calls
// Backend.Init() unconditionally (handlers/ec2/volume/service_impl.go), so it
// needs a reachable Predastore to do anything beyond input validation, while
// every other DaemonLite-wired resource is happy with an in-memory stand-in.
// Only a test that actually exercises volume storage should pay the shared
// predastore daemon's startup cost.
//
// Must be called before a test issues ec2.CreateVolume/DeleteVolume/
// DescribeVolumes; like StartECRDaemonLite it wires its own subjects
// independent of StartDaemonLite.
func StartVolumeDaemonLite(t *testing.T, gw *Gateway) *handlers_ec2_volume.VolumeServiceImpl {
t.Helper()
fixture := testpredastore.Start(t)
store := objectstore.NewS3ObjectStoreFromConfig(fixture.Host, fixture.Region, fixture.AccessKey, fixture.SecretKey)
require.NoError(t, store.EnsureBucket(context.Background(), testVolumeBucket), "ensure volume test bucket")
cfg := &config.Config{
AZ: testAZ,
Predastore: config.PredastoreConfig{
Host: fixture.Host,
Bucket: testVolumeBucket,
Region: fixture.Region,
AccessKey: fixture.AccessKey,
SecretKey: fixture.SecretKey,
},
// Real WAL/chunk files land here (viperblock.VB.BaseDir); a per-test
// dir is safe since only the predastore daemon itself is shared.
WalDir: t.TempDir(),
}
nc := gw.NATSConn
svc := handlers_ec2_volume.NewVolumeServiceImplWithStore(cfg, store, nc)
sub(t, nc, "ec2.CreateVolume", func(m *nats.Msg) { dispatch(m, svc.CreateVolume) })
sub(t, nc, "ec2.DeleteVolume", func(m *nats.Msg) { dispatch(m, svc.DeleteVolume) })
sub(t, nc, "ec2.DescribeVolumes", func(m *nats.Msg) { dispatch(m, svc.DescribeVolumes) })
return svc
}
+49
View File
@@ -0,0 +1,49 @@
//go:build integration
package integration
import (
"testing"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// TestCreateVolume_PersistsToRealPredastore is the storage-touching proof
// case for the real-predastore fixture (StartVolumeDaemonLite): it drives
// ec2.CreateVolume through the real gateway and a real VolumeServiceImpl,
// which constructs viperblock.New and calls Backend.Init()/SaveState()
// against an actual predastore daemon rather than an unreachable host. The
// previous unit-test equivalent (handlers/ec2/volume's
// TestCreateVolume_PassesValidation) could only assert the returned error
// wasn't a validation error, because nothing past validation could execute
// without a live backend. Here CreateVolume is expected to fully succeed,
// and DescribeVolumes reloads the volume from that same real backend
// (config.json read back over S3) rather than from any in-memory state the
// handler might be holding, so this proves the volume was actually
// persisted.
func TestCreateVolume_PersistsToRealPredastore(t *testing.T) {
gw := StartGateway(t)
StartVolumeDaemonLite(t, gw)
client := gw.EC2Client(t)
out, err := client.CreateVolume(&ec2.CreateVolumeInput{
Size: aws.Int64(8),
AvailabilityZone: aws.String(testAZ),
})
require.NoError(t, err, "CreateVolume")
require.NotNil(t, out.VolumeId)
desc, err := client.DescribeVolumes(&ec2.DescribeVolumesInput{
VolumeIds: []*string{out.VolumeId},
})
require.NoError(t, err, "DescribeVolumes")
require.Len(t, desc.Volumes, 1, "volume should reload from the real predastore backend")
assert.Equal(t, aws.StringValue(out.VolumeId), aws.StringValue(desc.Volumes[0].VolumeId))
assert.Equal(t, int64(8), aws.Int64Value(desc.Volumes[0].Size))
assert.Equal(t, "gp3", aws.StringValue(desc.Volumes[0].VolumeType))
assert.Equal(t, "available", aws.StringValue(desc.Volumes[0].State))
}