Scert/backend/internal/dnsprovider/aliyun_reconcile_test.go

151 lines
5.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package dnsprovider
import (
"context"
"encoding/json"
"errors"
"net/http"
"net/http/httptest"
"strings"
"sync"
"testing"
"time"
openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client"
"github.com/alibabacloud-go/tea/dara"
"github.com/go-acme/alidns-20150109/v5/client"
)
func TestAliyunAuthorizationLostAddResponse(t *testing.T) {
for _, scenario := range []string{"cleanup", "wrong_value", "wrong_zone", "wrong_rr", "wrong_type", "empty_id", "multiple", "missing", "incomplete", "query_error", "delete_error"} {
t.Run(scenario, func(t *testing.T) {
var mu sync.Mutex
var actions []string
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_ = r.ParseForm()
action := r.Header.Get("x-acs-action")
mu.Lock()
actions = append(actions, action)
mu.Unlock()
w.Header().Set("Content-Type", "application/json")
switch action {
case "AddDomainRecord":
// 模拟云端已经接受写入,但创建响应在传输中丢失。
connection, _, err := w.(http.Hijacker).Hijack()
if err != nil {
t.Error(err)
return
}
_ = connection.Close()
case "DescribeDomainRecords":
for key, expected := range map[string]string{
"DomainName": "example.com", "RRKeyWord": "_scert-authorization-unique.web",
"TypeKeyWord": "TXT", "ValueKeyWord": "unique-proof", "SearchMode": "COMBINATION",
} {
if r.Form.Get(key) != expected {
t.Errorf("补偿查询未限定本次记录:%s", key)
}
}
if scenario == "query_error" {
w.WriteHeader(http.StatusForbidden)
_, _ = w.Write([]byte(`{"Code":"Forbidden.RAM","Message":"denied"}`))
return
}
record := map[string]string{"RecordId": "owned-id", "DomainName": "example.com", "RR": "_scert-authorization-unique.web", "Type": "TXT", "Value": "unique-proof"}
switch scenario {
case "wrong_value":
record["Value"] = "other-proof"
case "wrong_zone":
record["DomainName"] = "other.com"
case "wrong_rr":
record["RR"] = "other"
case "wrong_type":
record["Type"] = "A"
case "empty_id":
record["RecordId"] = ""
}
records := []map[string]string{record}
if scenario == "multiple" {
records = append(records, record)
} else if scenario == "missing" {
records = nil
}
total := len(records)
if scenario == "incomplete" {
total++
}
_ = json.NewEncoder(w).Encode(map[string]any{"TotalCount": total, "DomainRecords": map[string]any{"Record": records}})
case "DeleteDomainRecord":
if r.Form.Get("RecordId") != "owned-id" {
t.Error("不得删除其他记录")
}
if scenario == "delete_error" {
w.WriteHeader(http.StatusForbidden)
_, _ = w.Write([]byte(`{"Code":"Forbidden.RAM","Message":"denied"}`))
return
}
_, _ = w.Write([]byte(`{"RecordId":"owned-id"}`))
default:
t.Errorf("不允许的云端操作:%s", action)
}
}))
defer server.Close()
sdk, err := client.NewClient(new(openapi.Config).SetAccessKeyId("test-id").SetAccessKeySecret("test-secret").
SetEndpoint(strings.TrimPrefix(server.URL, "http://")).SetProtocol("http").
SetRetryOptions(&dara.RetryOptions{Retryable: false, MaxAttempts: 1}))
if err != nil {
t.Fatal(err)
}
id, err := (aliyunRecords{client: sdk}).Add(t.Context(), "example.com", "_scert-authorization-unique.web", "unique-proof")
if err == nil || id != "" {
t.Fatal("创建结果不确定不能变成验证成功")
}
if scenario != "cleanup" && !strings.Contains(err.Error(), "需要核对") {
t.Fatalf("补偿未完成必须保留人工核对信息:%v", err)
}
if scenario != "cleanup" && !strings.Contains(DiagnoseAliyunError(err, "example.com"), "_scert-authorization-unique.web.example.com") {
t.Fatal("页面诊断必须保留待核对记录,不能只显示通用超时")
}
mu.Lock()
defer mu.Unlock()
want := "AddDomainRecord,DescribeDomainRecords"
if scenario == "cleanup" || scenario == "delete_error" {
want += ",DeleteDomainRecord"
}
if strings.Join(actions, ",") != want {
t.Fatalf("云端操作越界或自动重试创建:%v", actions)
}
})
}
}
func TestAliyunReconcilePreservesCancellationAndExclusions(t *testing.T) {
api := aliyunRecords{}
denied := errors.New("Forbidden.RAM")
if got := api.reconcileAuthorizationAdd(t.Context(), "example.com", "_scert-authorization-unique", "proof", denied); got != denied {
t.Fatal("明确拒绝不应触发补偿查询")
}
if got := api.reconcileAuthorizationAdd(t.Context(), "example.com", "_acme-challenge", "proof", context.DeadlineExceeded); got != context.DeadlineExceeded {
t.Fatal("不得通过授权探测补偿删除共享 ACME 记录")
}
ctx, cancel := context.WithCancel(t.Context())
cancel()
calls := 0
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
calls++
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"TotalCount":0,"DomainRecords":{"Record":[]}}`))
}))
defer server.Close()
sdk, err := client.NewClient(new(openapi.Config).SetAccessKeyId("test-id").SetAccessKeySecret("test-secret").SetEndpoint(strings.TrimPrefix(server.URL, "http://")).SetProtocol("http"))
if err != nil {
t.Fatal(err)
}
api.client = sdk
started := time.Now()
err = api.reconcileAuthorizationAdd(ctx, "example.com", "_scert-authorization-unique", "proof", context.Canceled)
if !errors.Is(err, context.Canceled) || calls != 1 || time.Since(started) > aliyunReconciliationTimeout {
t.Fatalf("补偿应独立于取消且保持时限和原始错误:calls=%d err=%v", calls, err)
}
}