151 lines
5.6 KiB
Go
151 lines
5.6 KiB
Go
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)
|
||
}
|
||
}
|