diff options
Diffstat (limited to 'provider/fake_porkbun_test.go')
| -rw-r--r-- | provider/fake_porkbun_test.go | 182 |
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 +} |