package cmd import ( "fmt" "net/http" "net/http/httptest" "strings" "sync/atomic" "testing" "github.com/minio/minio-go/v7" "github.com/minio/minio/internal/bucket/replication" xhttp "github.com/minio/minio/internal/http" ) func TestReviewR6LegacyPurgeRetries(t *testing.T) { var deletes atomic.Int32 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Method == http.MethodHead { w.Header().Set(xhttp.AmzDeleteMarker, "true") w.WriteHeader(http.StatusMethodNotAllowed) return } deletes.Add(1) w.WriteHeader(http.StatusNoContent) })) defer server.Close() client, err := minio.New(strings.TrimPrefix(server.URL, "http://"), &minio.Options{Region: "us-east-1", MaxRetries: 1}) if err != nil { t.Fatal(err) } old := globalBucketTargetSys globalBucketTargetSys = &BucketTargetSys{hc: map[string]epHealth{client.EndpointURL().Host: {Online: true}}} defer func() { globalBucketTargetSys = old }() d := DeletedObjectReplicationInfo{Bucket: "source", DeletedObject: DeletedObject{ObjectName: "marker", DeleteMarker: true, DeleteMarkerVersionID: mustGetUUID()}} d.ReplicationState.Targets = map[string]replication.StatusType{"arn1": replication.Completed} d.ReplicationState.PurgeTargets = map[string]VersionPurgeStatusType{"arn1": replication.VersionPurgePending} result := replicateDeleteToTarget(t.Context(), d, &TargetClient{Client: client, ARN: "arn1", Bucket: "target"}) t.Logf("remote DELETE calls=%d, creation=%s, purge=%s", deletes.Load(), result.ReplicationStatus, result.VersionPurgeStatus) if deletes.Load() != 1 || result.VersionPurgeStatus != replication.VersionPurgeComplete { t.Error("pending legacy purge was not delivered") } } func TestReviewR6PurgeExits(t *testing.T) { for _, tc := range []struct{name string; creation replication.StatusType; purge VersionPurgeStatusType; head int; existing bool; want VersionPurgeStatusType; wantCalls int; wantReset bool}{ {"head_forbidden", replication.Pending, replication.VersionPurgePending, 403, false, replication.VersionPurgeFailed, 1, false}, {"failed_resync", replication.Completed, replication.VersionPurgePending, 405, true, replication.VersionPurgeFailed, 2, false}, {"already_purged", replication.Pending, replication.VersionPurgeComplete, 405, false, replication.VersionPurgeComplete, 0, false}, } { t.Run(tc.name, func(t *testing.T){ var calls atomic.Int32 server:=httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter,r *http.Request){ calls.Add(1) if r.Method==http.MethodHead {w.Header().Set(xhttp.AmzDeleteMarker,"true"); w.WriteHeader(tc.head);return} w.WriteHeader(403);fmt.Fprint(w,`AccessDenied`) })) defer server.Close() client,err:=minio.New(strings.TrimPrefix(server.URL,"http://"),&minio.Options{Region:"us-east-1",MaxRetries:1});if err!=nil{t.Fatal(err)} old:=globalBucketTargetSys;globalBucketTargetSys=&BucketTargetSys{hc:map[string]epHealth{client.EndpointURL().Host:{Online:true}}};defer func(){globalBucketTargetSys=old}() d:=DeletedObjectReplicationInfo{Bucket:"source",DeletedObject:DeletedObject{ObjectName:"marker",DeleteMarker:true,DeleteMarkerVersionID:mustGetUUID()}} d.ReplicationState.Targets=map[string]replication.StatusType{"arn1":tc.creation} d.ReplicationState.PurgeTargets=map[string]VersionPurgeStatusType{"arn1":tc.purge} if tc.existing {d.OpType=replication.ExistingObjectReplicationType} got:=replicateDeleteToTarget(t.Context(),d,&TargetClient{Client:client,ARN:"arn1",Bucket:"target",ResetID:"new-reset"}) t.Logf("calls=%d, creation=%s, purge=%s, reset=%q",calls.Load(),got.ReplicationStatus,got.VersionPurgeStatus,got.ResyncTimestamp) if got.ReplicationStatus!=tc.creation || got.VersionPurgeStatus!=tc.want || int(calls.Load())!=tc.wantCalls || (got.ResyncTimestamp!="")!=tc.wantReset {t.Errorf("operation classified inconsistently: %+v",got)} }) } }