Scert/backend/internal/localreset/recovery_reconciliation_test.go

115 lines
3.9 KiB
Go

package localreset
import (
"context"
"strings"
"testing"
"time"
"git.pcux.cn/xuans/Scert/backend/internal/deployment"
)
type reconciliationSession struct{ dispositionTestSession }
func (s *reconciliationSession) PrepareRecoveryReconciliation(ctx context.Context, sourceID string) (RecoveryPreparation, error) {
value, err := s.PrepareRecovery(ctx, "tx-1", deployment.RecoveryActionVerifyCommit)
value.Summary.SourceDispositionID = sourceID
value.Summary.Phase = deployment.TransactionPhaseCommitVerified
s.prepared = value
return value, err
}
func (s *reconciliationSession) ExecuteRecovery(ctx context.Context, id, token string) (deployment.RecoveryDispositionSummary, error) {
sourceID := s.prepared.Summary.SourceDispositionID
value, err := s.dispositionTestSession.ExecuteRecovery(ctx, id, token)
if err != nil {
return value, err
}
value.SourceDispositionID, value.ResultCode = sourceID, deployment.RecoveryResultCommitted
s.result = value
return value, nil
}
func TestRecoveryReconciliationProtocolAndQuery(t *testing.T) {
session := &reconciliationSession{}
_, conn, done := recoveryConnection(t, session)
client, err := Begin(t.Context(), conn)
if err != nil {
t.Fatal(err)
}
sourceID := strings.Repeat("a", 64)
prepared, err := client.PrepareRecoveryReconciliation(sourceID)
if err != nil || prepared.Summary.SourceDispositionID != sourceID {
t.Fatal("原处置绑定丢失", err)
}
result, err := client.ExecuteRecovery(prepared.DispositionID, prepared.Token)
if err != nil || result.SourceDispositionID != sourceID || result.Status != "succeeded" {
t.Fatal("重新核查结果无效", err)
}
queried, err := client.GetRecoveryDisposition(result.ID)
if err != nil || queried != result {
t.Fatal("查询改变了关联结果", err)
}
if _, err := client.ExecuteRecovery(prepared.DispositionID, prepared.Token); err == nil {
t.Fatal("旧令牌被重放")
}
_ = client.Close()
<-done
if session.prepares.Load() != 1 || session.executes.Load() != 2 || session.queries.Load() != 1 || session.resets.Load() != 0 {
t.Fatal("调用了错误业务入口")
}
}
func TestRecoveryReconciliationRejectsUnboundResponse(t *testing.T) {
sourceID := strings.Repeat("a", 64)
session := &reconciliationSession{}
prepared, err := session.PrepareRecoveryReconciliation(t.Context(), sourceID)
if err != nil {
t.Fatal(err)
}
request := Request{Action: "recovery_reconcile_prepare", DispositionID: sourceID}
for _, mode := range []string{"source", "action", "same-id", "transaction", "mixed", "expired-range"} {
t.Run(mode, func(t *testing.T) {
value := prepared
response := Response{Preparation: &value}
switch mode {
case "source":
value.Summary.SourceDispositionID = strings.Repeat("d", 64)
case "action":
value.Summary.Action = deployment.RecoveryActionCommit
case "same-id":
value.DispositionID = sourceID
case "transaction":
value.Summary.TransactionID = "../bad"
case "mixed":
response.Disposition = &deployment.RecoveryDispositionSummary{}
case "expired-range":
value.Summary.ExpiresAt = value.Summary.ObservedAt.Add(2 * time.Minute)
}
if validDispositionResponse(request, response) {
t.Fatal("接受了混合或无绑定准备响应")
}
})
}
if validRecoveryAction(deployment.RecoveryActionVerifyCommit) || validRecoveryAction(deployment.RecoveryActionVerifyRollback) {
t.Fatal("普通准备允许绕过原处置绑定")
}
}
func TestRecoveryReconciliationOldServerFailsClosed(t *testing.T) {
session := &dispositionTestSession{}
_, conn, done := recoveryConnection(t, session)
client, err := Begin(t.Context(), conn)
if err != nil {
t.Fatal(err)
}
if _, err := client.PrepareRecoveryReconciliation(strings.Repeat("a", 64)); err == nil {
t.Fatal("旧服务错误地支持了重新核查")
}
_ = client.Close()
<-done
if session.prepares.Load() != 0 || session.executes.Load() != 0 {
t.Fatal("旧服务错误地执行了原动作")
}
}