aboutsummaryrefslogtreecommitdiff
path: root/provider/fake_porkbun_test.go
diff options
context:
space:
mode:
authorRuben Beltran del Rio <jj@r.bdr.sh>2026-07-03 15:21:56 +0200
committerRuben Beltran del Rio <jj@r.bdr.sh>2026-07-03 15:21:58 +0200
commit95dc66cb108a311065917362a00b86c08767303d (patch)
tree46ca65be6548224be0dd5a42bb86f2ecede14da8 /provider/fake_porkbun_test.go
Add source controlHEADmain
Diffstat (limited to 'provider/fake_porkbun_test.go')
-rw-r--r--provider/fake_porkbun_test.go182
1 files changed, 182 insertions, 0 deletions
diff --git a/provider/fake_porkbun_test.go b/provider/fake_porkbun_test.go
new file mode 100644
index 0000000..a6a602d
--- /dev/null
+++ b/provider/fake_porkbun_test.go
@@ -0,0 +1,182 @@
+package provider_test
+
+import (
+ "encoding/json"
+ "fmt"
+ "net/http"
+ "strconv"
+ "strings"
+ "sync"
+ "testing"
+)
+
+const (
+ testAPIKey = "pk1_test"
+ testSecretKey = "sk1_test"
+)
+
+type fakeRecord struct {
+ name string
+ typ string
+ content string
+ ttl int
+ prio int
+}
+
+type fakePorkbun struct {
+ t *testing.T
+
+ mu sync.Mutex
+ nextID int
+ records map[string]map[string]*fakeRecord
+
+ createCalls, editCalls, deleteCalls, retrieveCalls int
+}
+
+func newFakePorkbun(t *testing.T) *fakePorkbun {
+ return &fakePorkbun{t: t, nextID: 1000, records: map[string]map[string]*fakeRecord{}}
+}
+
+func (f *fakePorkbun) seed(domain string, rec fakeRecord) string {
+ f.mu.Lock()
+ defer f.mu.Unlock()
+ f.nextID++
+ id := strconv.Itoa(f.nextID)
+ if f.records[domain] == nil {
+ f.records[domain] = map[string]*fakeRecord{}
+ }
+ f.records[domain][id] = &rec
+ return id
+}
+
+func (f *fakePorkbun) record(domain, id string) *fakeRecord {
+ f.mu.Lock()
+ defer f.mu.Unlock()
+ rec, ok := f.records[domain][id]
+ if !ok {
+ return nil
+ }
+ cp := *rec
+ return &cp
+}
+
+func (f *fakePorkbun) recordCount() int {
+ f.mu.Lock()
+ defer f.mu.Unlock()
+ n := 0
+ for _, recs := range f.records {
+ n += len(recs)
+ }
+ return n
+}
+
+func (f *fakePorkbun) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+ f.mu.Lock()
+ defer f.mu.Unlock()
+
+ var body map[string]any
+ if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
+ f.fail(w, http.StatusBadRequest, "Invalid JSON.")
+ return
+ }
+ if body["apikey"] != testAPIKey || body["secretapikey"] != testSecretKey {
+ f.fail(w, http.StatusForbidden, "Invalid API key. (002)")
+ return
+ }
+
+ parts := strings.Split(strings.Trim(r.URL.Path, "/"), "/")
+ switch {
+ case len(parts) == 1 && parts[0] == "ping":
+ f.ok(w, map[string]any{"yourIp": "203.0.113.7"})
+
+ case len(parts) == 3 && parts[0] == "dns" && parts[1] == "create":
+ f.createCalls++
+ domain := parts[2]
+ f.nextID++
+ id := strconv.Itoa(f.nextID)
+ if f.records[domain] == nil {
+ f.records[domain] = map[string]*fakeRecord{}
+ }
+ f.records[domain][id] = &fakeRecord{
+ name: str(body["name"]),
+ typ: str(body["type"]),
+ content: str(body["content"]),
+ ttl: atoi(f.t, str(body["ttl"])),
+ prio: atoi(f.t, str(body["prio"])),
+ }
+
+ f.ok(w, map[string]any{"id": f.nextID})
+
+ case len(parts) == 4 && parts[0] == "dns" && parts[1] == "edit":
+ f.editCalls++
+ rec, ok := f.records[parts[2]][parts[3]]
+ if !ok {
+ f.fail(w, http.StatusBadRequest, "Invalid record ID.")
+ return
+ }
+ rec.typ = str(body["type"])
+ rec.content = str(body["content"])
+ rec.ttl = atoi(f.t, str(body["ttl"]))
+ rec.prio = atoi(f.t, str(body["prio"]))
+ f.ok(w, nil)
+
+ case len(parts) == 4 && parts[0] == "dns" && parts[1] == "delete":
+ f.deleteCalls++
+ if _, ok := f.records[parts[2]][parts[3]]; !ok {
+ f.fail(w, http.StatusBadRequest, "Invalid record ID.")
+ return
+ }
+ delete(f.records[parts[2]], parts[3])
+ f.ok(w, nil)
+
+ case len(parts) == 4 && parts[0] == "dns" && parts[1] == "retrieve":
+ f.retrieveCalls++
+ domain, id := parts[2], parts[3]
+ records := []map[string]any{}
+ if rec, ok := f.records[domain][id]; ok {
+ fullName := domain
+ if rec.name != "" {
+ fullName = rec.name + "." + domain
+ }
+
+ records = append(records, map[string]any{
+ "id": id, "name": fullName, "type": rec.typ, "content": rec.content,
+ "ttl": strconv.Itoa(rec.ttl), "prio": strconv.Itoa(rec.prio), "notes": "",
+ })
+ }
+ f.ok(w, map[string]any{"records": records})
+
+ default:
+ f.fail(w, http.StatusNotFound, fmt.Sprintf("Unknown endpoint %q.", r.URL.Path))
+ }
+}
+
+func (f *fakePorkbun) ok(w http.ResponseWriter, extra map[string]any) {
+ resp := map[string]any{"status": "SUCCESS"}
+ for k, v := range extra {
+ resp[k] = v
+ }
+ json.NewEncoder(w).Encode(resp)
+}
+
+func (f *fakePorkbun) fail(w http.ResponseWriter, code int, message string) {
+ w.WriteHeader(code)
+ json.NewEncoder(w).Encode(map[string]any{"status": "ERROR", "message": message})
+}
+
+func str(v any) string {
+ s, _ := v.(string)
+ return s
+}
+
+func atoi(t *testing.T, s string) int {
+ t.Helper()
+ if s == "" {
+ return 0
+ }
+ n, err := strconv.Atoi(s)
+ if err != nil {
+ t.Errorf("fake porkbun: expected numeric string, got %q", s)
+ }
+ return n
+}