mirror of
https://github.com/jhd3197/ServerKit.git
synced 2026-11-02 10:07:29 +09:00
202 lines
9.3 KiB
Python
202 lines
9.3 KiB
Python
"""Proving tests for the shared Cloudflare client (DNS unification #2).
|
|
|
|
Both the provider layer (DNSProviderService) and the zone layer (DNSZoneService)
|
|
now talk to Cloudflare through ``app.services.dns.cloudflare.CloudflareClient``.
|
|
These tests pin the behavior that used to be duplicated (and had drifted):
|
|
|
|
* auth header shape — scoped token vs global key,
|
|
* CAA structured ``data`` (never a flat ``content``),
|
|
* MX priority parsed out of the value,
|
|
* idempotent upsert — PUT an existing record instead of POSTing a duplicate,
|
|
* delete by id (no lookup) vs by name (lookup-then-delete),
|
|
* and the zone path capturing ``provider_record_id`` after a create.
|
|
|
|
Requests are stubbed by monkeypatching the ``requests`` object the client
|
|
imported; ``requests`` is a shared module, so this also covers the provider
|
|
layer's delegations.
|
|
"""
|
|
|
|
|
|
class _Resp:
|
|
def __init__(self, js):
|
|
self._js = js
|
|
|
|
def json(self):
|
|
return self._js
|
|
|
|
|
|
def _client(token='tok', email=None):
|
|
from app.services.dns import cloudflare as cf
|
|
from app.services.dns.base import DnsCredential
|
|
return cf.CloudflareClient(DnsCredential(provider='cloudflare', token=token, email=email))
|
|
|
|
|
|
# ── auth headers ─────────────────────────────────────────────────────────────
|
|
|
|
def test_headers_scoped_token_vs_global_key():
|
|
bearer = _client(token='tok')._headers()
|
|
assert bearer['Authorization'] == 'Bearer tok'
|
|
assert 'X-Auth-Email' not in bearer
|
|
|
|
gk = _client(token='globalkey', email='a@b.com')._headers()
|
|
assert gk['X-Auth-Email'] == 'a@b.com' and gk['X-Auth-Key'] == 'globalkey'
|
|
assert 'Authorization' not in gk
|
|
|
|
|
|
# ── payload wire format ──────────────────────────────────────────────────────
|
|
|
|
def test_payload_a_record_carries_proxied_not_data():
|
|
from app.services.dns.base import DnsRecordSpec
|
|
p = _client()._payload(DnsRecordSpec('A', 'www.example.com', '1.2.3.4', proxied=True))
|
|
assert p['content'] == '1.2.3.4' and p['proxied'] is True
|
|
assert 'data' not in p
|
|
|
|
|
|
def test_payload_caa_uses_structured_data():
|
|
from app.services.dns.base import DnsRecordSpec
|
|
p = _client()._payload(DnsRecordSpec('CAA', 'example.com', '0 issue "letsencrypt.org"'))
|
|
assert p['data'] == {'flags': 0, 'tag': 'issue', 'value': 'letsencrypt.org'}
|
|
assert 'content' not in p and 'proxied' not in p
|
|
|
|
|
|
def test_payload_mx_priority_parsed_from_value():
|
|
from app.services.dns.base import DnsRecordSpec
|
|
p = _client()._payload(DnsRecordSpec('MX', 'example.com', '10 mail.example.com'))
|
|
assert p['priority'] == 10 and p['content'] == 'mail.example.com'
|
|
|
|
|
|
def test_payload_mx_explicit_priority_wins():
|
|
from app.services.dns.base import DnsRecordSpec
|
|
p = _client()._payload(DnsRecordSpec('MX', 'example.com', 'mail.example.com', priority=20))
|
|
assert p['priority'] == 20 and p['content'] == 'mail.example.com'
|
|
|
|
|
|
# ── upsert (idempotent) ──────────────────────────────────────────────────────
|
|
|
|
def test_upsert_posts_when_absent(monkeypatch):
|
|
from app.services.dns import cloudflare as cf
|
|
from app.services.dns.base import DnsRecordSpec
|
|
monkeypatch.setattr(cf.requests, 'get',
|
|
lambda url, headers=None, timeout=None: _Resp({'result': []}))
|
|
seen = {}
|
|
monkeypatch.setattr(cf.requests, 'post',
|
|
lambda url, headers=None, json=None, timeout=None:
|
|
(seen.update(verb='post', url=url) or
|
|
_Resp({'success': True, 'result': {'id': 'NEW'}})))
|
|
monkeypatch.setattr(cf.requests, 'put',
|
|
lambda *a, **k: (seen.update(verb='put') or _Resp({'success': False})))
|
|
|
|
res = _client().upsert('zoneABC', DnsRecordSpec('A', 'www.example.com', '1.2.3.4'))
|
|
assert res['success'] is True and res['record_id'] == 'NEW'
|
|
assert seen['verb'] == 'post' and seen['url'].endswith('/zones/zoneABC/dns_records')
|
|
|
|
|
|
def test_upsert_puts_existing_match_by_name(monkeypatch):
|
|
from app.services.dns import cloudflare as cf
|
|
from app.services.dns.base import DnsRecordSpec
|
|
monkeypatch.setattr(cf.requests, 'get',
|
|
lambda url, headers=None, timeout=None: _Resp({'result': [{'id': 'rec1'}]}))
|
|
seen = {}
|
|
monkeypatch.setattr(cf.requests, 'put',
|
|
lambda url, headers=None, json=None, timeout=None:
|
|
(seen.update(url=url) or _Resp({'success': True, 'result': {'id': 'rec1'}})))
|
|
monkeypatch.setattr(cf.requests, 'post',
|
|
lambda *a, **k: (seen.update(verb='post') or _Resp({'success': False})))
|
|
|
|
res = _client().upsert('z', DnsRecordSpec('A', 'www.example.com', '5.6.7.8'))
|
|
assert res['record_id'] == 'rec1'
|
|
assert seen.get('verb') != 'post' and seen['url'].endswith('/rec1')
|
|
|
|
|
|
def test_upsert_by_record_id_skips_lookup(monkeypatch):
|
|
from app.services.dns import cloudflare as cf
|
|
from app.services.dns.base import DnsRecordSpec
|
|
counters = {'get': 0}
|
|
monkeypatch.setattr(cf.requests, 'get',
|
|
lambda *a, **k: (counters.update(get=counters['get'] + 1) or _Resp({'result': []})))
|
|
seen = {}
|
|
monkeypatch.setattr(cf.requests, 'put',
|
|
lambda url, headers=None, json=None, timeout=None:
|
|
(seen.update(url=url) or _Resp({'success': True, 'result': {'id': 'rid'}})))
|
|
|
|
res = _client().upsert('z', DnsRecordSpec('A', 'www.example.com', '1.2.3.4'), record_id='rid')
|
|
assert res['success'] is True and counters['get'] == 0
|
|
assert seen['url'].endswith('/rid')
|
|
|
|
|
|
# ── delete ───────────────────────────────────────────────────────────────────
|
|
|
|
def test_delete_by_record_id_skips_lookup(monkeypatch):
|
|
from app.services.dns import cloudflare as cf
|
|
counters = {'get': 0}
|
|
monkeypatch.setattr(cf.requests, 'get',
|
|
lambda *a, **k: (counters.update(get=counters['get'] + 1) or _Resp({'result': []})))
|
|
deleted = []
|
|
monkeypatch.setattr(cf.requests, 'delete',
|
|
lambda url, headers=None, timeout=None: (deleted.append(url) or _Resp({'success': True})))
|
|
|
|
res = _client().delete('z', record_id='rid')
|
|
assert res['success'] is True and counters['get'] == 0
|
|
assert deleted == ['https://api.cloudflare.com/client/v4/zones/z/dns_records/rid']
|
|
|
|
|
|
def test_delete_by_name_removes_all_matches(monkeypatch):
|
|
from app.services.dns import cloudflare as cf
|
|
monkeypatch.setattr(cf.requests, 'get',
|
|
lambda url, headers=None, timeout=None: _Resp({'result': [{'id': 'a'}, {'id': 'b'}]}))
|
|
deleted = []
|
|
monkeypatch.setattr(cf.requests, 'delete',
|
|
lambda url, headers=None, timeout=None: (deleted.append(url) or _Resp({'success': True})))
|
|
|
|
res = _client().delete('z', record_type='A', name='www.example.com')
|
|
assert res['success'] is True and len(deleted) == 2
|
|
|
|
|
|
def test_delete_missing_is_success(monkeypatch):
|
|
from app.services.dns import cloudflare as cf
|
|
monkeypatch.setattr(cf.requests, 'get',
|
|
lambda url, headers=None, timeout=None: _Resp({'result': []}))
|
|
monkeypatch.setattr(cf.requests, 'delete',
|
|
lambda *a, **k: _Resp({'success': True}))
|
|
res = _client().delete('z', record_type='A', name='gone.example.com')
|
|
assert res['success'] is True and 'already deleted' in res['message']
|
|
|
|
|
|
def test_delete_stale_record_id_is_idempotent(monkeypatch):
|
|
from app.services.dns import cloudflare as cf
|
|
monkeypatch.setattr(
|
|
cf.requests, 'delete',
|
|
lambda *a, **k: _Resp({
|
|
'success': False,
|
|
'errors': [{'code': 81044, 'message': 'DNS record does not exist'}],
|
|
}),
|
|
)
|
|
res = _client().delete('z', record_id='stale')
|
|
assert res['success'] is True
|
|
|
|
|
|
# ── zone layer regression: capture provider_record_id on create ──────────────
|
|
|
|
def test_zone_create_record_persists_provider_record_id(app, monkeypatch):
|
|
"""DNSZoneService.create_record on a Cloudflare zone must store the id Cloudflare
|
|
returns, so later updates/deletes address the record directly."""
|
|
from app import db
|
|
from app.models.dns_zone import DNSZone
|
|
from app.services.dns_zone_service import DNSZoneService
|
|
from app.services.dns import cloudflare as cf
|
|
|
|
zone = DNSZone(domain='example.com', provider='cloudflare', provider_zone_id='zoneABC')
|
|
zone.provider_config = {'api_token': 'tok'}
|
|
db.session.add(zone)
|
|
db.session.commit()
|
|
|
|
monkeypatch.setattr(cf.requests, 'get',
|
|
lambda url, headers=None, timeout=None: _Resp({'result': []}))
|
|
monkeypatch.setattr(cf.requests, 'post',
|
|
lambda url, headers=None, json=None, timeout=None:
|
|
_Resp({'success': True, 'result': {'id': 'CFREC'}}))
|
|
|
|
rec = DNSZoneService.create_record(
|
|
zone.id, {'record_type': 'A', 'name': 'www.example.com', 'content': '1.2.3.4'})
|
|
assert rec.provider_record_id == 'CFREC'
|