diff --git a/test/volume_server/framework/volume_fixture.go b/test/volume_server/framework/volume_fixture.go index f6229f3f8..7ff710ccb 100644 --- a/test/volume_server/framework/volume_fixture.go +++ b/test/volume_server/framework/volume_fixture.go @@ -14,18 +14,26 @@ import ( func AllocateVolume(t testing.TB, client volume_server_pb.VolumeServerClient, volumeID uint32, collection string) { t.Helper() + AllocateVolumeWithReplication(t, client, volumeID, collection, "000") +} + +func AllocateVolumeWithReplication(t testing.TB, client volume_server_pb.VolumeServerClient, volumeID uint32, collection, replication string) { + t.Helper() ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() + if replication == "" { + replication = "000" + } _, err := client.AllocateVolume(ctx, &volume_server_pb.AllocateVolumeRequest{ VolumeId: volumeID, Collection: collection, - Replication: "000", + Replication: replication, Version: uint32(needle.GetCurrentVersion()), }) if err != nil { - t.Fatalf("allocate volume %d: %v", volumeID, err) + t.Fatalf("allocate volume %d (replication=%s): %v", volumeID, replication, err) } } diff --git a/test/volume_server/http/write_error_variants_test.go b/test/volume_server/http/write_error_variants_test.go index ce2dd01d8..536180829 100644 --- a/test/volume_server/http/write_error_variants_test.go +++ b/test/volume_server/http/write_error_variants_test.go @@ -102,3 +102,35 @@ func TestWriteRejectsPayloadOverFileSizeLimit(t *testing.T) { t.Fatalf("oversized write response should mention limit, got %q", string(oversizedBody)) } } + +func TestReplicatedWriteFailsWhenReplicaRequirementsNotMet(t *testing.T) { + if testing.Short() { + t.Skip("skipping integration test in short mode") + } + + clusterHarness := framework.StartSingleVolumeCluster(t, matrix.P1()) + conn, grpcClient := framework.DialVolumeServer(t, clusterHarness.VolumeGRPCAddress()) + defer conn.Close() + + const volumeID = uint32(109) + framework.AllocateVolumeWithReplication(t, grpcClient, volumeID, "", "001") + + client := framework.NewHTTPClient() + fid := framework.NewFileID(volumeID, 772003, 0x3A4B5C6D) + payload := []byte("replicated-write-failure-path") + + writeResp := framework.UploadBytes(t, client, clusterHarness.VolumeAdminURL(), fid, payload) + writeBody := framework.ReadAllAndClose(t, writeResp) + if writeResp.StatusCode != http.StatusInternalServerError { + t.Fatalf("replicated write with unmet replication requirements expected 500, got %d body=%s", writeResp.StatusCode, string(writeBody)) + } + if !strings.Contains(strings.ToLower(string(writeBody)), "replica") { + t.Fatalf("replicated write failure response should mention replica write failure, got %q", string(writeBody)) + } + + readResp := framework.ReadBytes(t, client, clusterHarness.VolumeAdminURL(), fid) + _ = framework.ReadAllAndClose(t, readResp) + if readResp.StatusCode != http.StatusNotFound { + t.Fatalf("local read after failed replicate write expected 404 (not committed locally), got %d", readResp.StatusCode) + } +}