From 02f4eb46a5a1ef6922a457a5c5a29d8e79946dcf Mon Sep 17 00:00:00 2001 From: joshsiv-mulga Date: Mon, 20 Jul 2026 16:58:09 +1000 Subject: [PATCH] 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. --- tests/integration/volume_daemon_lite.go | 71 +++++++++++++++++++++++++ tests/integration/volume_test.go | 49 +++++++++++++++++ 2 files changed, 120 insertions(+) create mode 100644 tests/integration/volume_daemon_lite.go create mode 100644 tests/integration/volume_test.go diff --git a/tests/integration/volume_daemon_lite.go b/tests/integration/volume_daemon_lite.go new file mode 100644 index 000000000..c65c3dd8a --- /dev/null +++ b/tests/integration/volume_daemon_lite.go @@ -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 +} diff --git a/tests/integration/volume_test.go b/tests/integration/volume_test.go new file mode 100644 index 000000000..7ed1de0e9 --- /dev/null +++ b/tests/integration/volume_test.go @@ -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)) +}