Replace the CoreDNS precursor with a purpose-built authoritative DNS server. Zones and records (A, AAAA, CNAME) are stored in SQLite and managed via synchronized gRPC + REST APIs authenticated through MCIAS. Non-authoritative queries are forwarded to upstream resolvers with in-memory caching. Key components: - DNS server (miekg/dns) with authoritative zone handling and forwarding - gRPC + REST management APIs with MCIAS auth (mcdsl integration) - SQLite storage with CNAME exclusivity enforcement and auto SOA serials - 30 tests covering database CRUD, DNS resolution, and caching Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
290 lines
7.7 KiB
Go
290 lines
7.7 KiB
Go
package db
|
|
|
|
import (
|
|
"errors"
|
|
"testing"
|
|
)
|
|
|
|
func createTestZone(t *testing.T, db *DB) *Zone {
|
|
t.Helper()
|
|
zone, err := db.CreateZone("svc.mcp.metacircular.net", "ns.mcp.metacircular.net.", "admin.metacircular.net.", 3600, 600, 86400, 300)
|
|
if err != nil {
|
|
t.Fatalf("create zone: %v", err)
|
|
}
|
|
return zone
|
|
}
|
|
|
|
func TestCreateRecordA(t *testing.T) {
|
|
db := openTestDB(t)
|
|
createTestZone(t, db)
|
|
|
|
record, err := db.CreateRecord("svc.mcp.metacircular.net", "metacrypt", "A", "192.168.88.181", 300)
|
|
if err != nil {
|
|
t.Fatalf("create record: %v", err)
|
|
}
|
|
if record.Name != "metacrypt" {
|
|
t.Fatalf("got name %q, want %q", record.Name, "metacrypt")
|
|
}
|
|
if record.Type != "A" {
|
|
t.Fatalf("got type %q, want %q", record.Type, "A")
|
|
}
|
|
if record.Value != "192.168.88.181" {
|
|
t.Fatalf("got value %q, want %q", record.Value, "192.168.88.181")
|
|
}
|
|
}
|
|
|
|
func TestCreateRecordAAAA(t *testing.T) {
|
|
db := openTestDB(t)
|
|
createTestZone(t, db)
|
|
|
|
record, err := db.CreateRecord("svc.mcp.metacircular.net", "metacrypt", "AAAA", "2001:db8::1", 300)
|
|
if err != nil {
|
|
t.Fatalf("create record: %v", err)
|
|
}
|
|
if record.Type != "AAAA" {
|
|
t.Fatalf("got type %q, want %q", record.Type, "AAAA")
|
|
}
|
|
}
|
|
|
|
func TestCreateRecordCNAME(t *testing.T) {
|
|
db := openTestDB(t)
|
|
createTestZone(t, db)
|
|
|
|
record, err := db.CreateRecord("svc.mcp.metacircular.net", "alias", "CNAME", "rift.mcp.metacircular.net.", 300)
|
|
if err != nil {
|
|
t.Fatalf("create record: %v", err)
|
|
}
|
|
if record.Type != "CNAME" {
|
|
t.Fatalf("got type %q, want %q", record.Type, "CNAME")
|
|
}
|
|
}
|
|
|
|
func TestCreateRecordInvalidIP(t *testing.T) {
|
|
db := openTestDB(t)
|
|
createTestZone(t, db)
|
|
|
|
_, err := db.CreateRecord("svc.mcp.metacircular.net", "bad", "A", "not-an-ip", 300)
|
|
if err == nil {
|
|
t.Fatal("expected error for invalid IPv4")
|
|
}
|
|
}
|
|
|
|
func TestCreateRecordCNAMEExclusivity(t *testing.T) {
|
|
db := openTestDB(t)
|
|
createTestZone(t, db)
|
|
|
|
// Create an A record first.
|
|
_, err := db.CreateRecord("svc.mcp.metacircular.net", "metacrypt", "A", "192.168.88.181", 300)
|
|
if err != nil {
|
|
t.Fatalf("create A record: %v", err)
|
|
}
|
|
|
|
// Trying to add a CNAME for the same name should fail.
|
|
_, err = db.CreateRecord("svc.mcp.metacircular.net", "metacrypt", "CNAME", "rift.mcp.metacircular.net.", 300)
|
|
if !errors.Is(err, ErrConflict) {
|
|
t.Fatalf("expected ErrConflict, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestCreateRecordCNAMEExclusivityReverse(t *testing.T) {
|
|
db := openTestDB(t)
|
|
createTestZone(t, db)
|
|
|
|
// Create a CNAME record first.
|
|
_, err := db.CreateRecord("svc.mcp.metacircular.net", "alias", "CNAME", "rift.mcp.metacircular.net.", 300)
|
|
if err != nil {
|
|
t.Fatalf("create CNAME record: %v", err)
|
|
}
|
|
|
|
// Trying to add an A record for the same name should fail.
|
|
_, err = db.CreateRecord("svc.mcp.metacircular.net", "alias", "A", "192.168.88.181", 300)
|
|
if !errors.Is(err, ErrConflict) {
|
|
t.Fatalf("expected ErrConflict, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestCreateRecordBumpsSerial(t *testing.T) {
|
|
db := openTestDB(t)
|
|
zone := createTestZone(t, db)
|
|
originalSerial := zone.Serial
|
|
|
|
_, err := db.CreateRecord("svc.mcp.metacircular.net", "metacrypt", "A", "192.168.88.181", 300)
|
|
if err != nil {
|
|
t.Fatalf("create record: %v", err)
|
|
}
|
|
|
|
updated, err := db.GetZone("svc.mcp.metacircular.net")
|
|
if err != nil {
|
|
t.Fatalf("get zone: %v", err)
|
|
}
|
|
if updated.Serial <= originalSerial {
|
|
t.Fatalf("serial should have bumped: %d <= %d", updated.Serial, originalSerial)
|
|
}
|
|
}
|
|
|
|
func TestListRecords(t *testing.T) {
|
|
db := openTestDB(t)
|
|
createTestZone(t, db)
|
|
|
|
_, err := db.CreateRecord("svc.mcp.metacircular.net", "metacrypt", "A", "192.168.88.181", 300)
|
|
if err != nil {
|
|
t.Fatalf("create record 1: %v", err)
|
|
}
|
|
_, err = db.CreateRecord("svc.mcp.metacircular.net", "metacrypt", "A", "100.95.252.120", 300)
|
|
if err != nil {
|
|
t.Fatalf("create record 2: %v", err)
|
|
}
|
|
_, err = db.CreateRecord("svc.mcp.metacircular.net", "mcr", "A", "192.168.88.181", 300)
|
|
if err != nil {
|
|
t.Fatalf("create record 3: %v", err)
|
|
}
|
|
|
|
// List all records.
|
|
records, err := db.ListRecords("svc.mcp.metacircular.net", "", "")
|
|
if err != nil {
|
|
t.Fatalf("list records: %v", err)
|
|
}
|
|
if len(records) != 3 {
|
|
t.Fatalf("got %d records, want 3", len(records))
|
|
}
|
|
|
|
// Filter by name.
|
|
records, err = db.ListRecords("svc.mcp.metacircular.net", "metacrypt", "")
|
|
if err != nil {
|
|
t.Fatalf("list records by name: %v", err)
|
|
}
|
|
if len(records) != 2 {
|
|
t.Fatalf("got %d records, want 2", len(records))
|
|
}
|
|
|
|
// Filter by type.
|
|
records, err = db.ListRecords("svc.mcp.metacircular.net", "", "A")
|
|
if err != nil {
|
|
t.Fatalf("list records by type: %v", err)
|
|
}
|
|
if len(records) != 3 {
|
|
t.Fatalf("got %d records, want 3", len(records))
|
|
}
|
|
}
|
|
|
|
func TestUpdateRecord(t *testing.T) {
|
|
db := openTestDB(t)
|
|
createTestZone(t, db)
|
|
|
|
record, err := db.CreateRecord("svc.mcp.metacircular.net", "metacrypt", "A", "192.168.88.181", 300)
|
|
if err != nil {
|
|
t.Fatalf("create record: %v", err)
|
|
}
|
|
|
|
updated, err := db.UpdateRecord(record.ID, "metacrypt", "A", "10.0.0.1", 600)
|
|
if err != nil {
|
|
t.Fatalf("update record: %v", err)
|
|
}
|
|
if updated.Value != "10.0.0.1" {
|
|
t.Fatalf("got value %q, want %q", updated.Value, "10.0.0.1")
|
|
}
|
|
if updated.TTL != 600 {
|
|
t.Fatalf("got ttl %d, want 600", updated.TTL)
|
|
}
|
|
}
|
|
|
|
func TestDeleteRecord(t *testing.T) {
|
|
db := openTestDB(t)
|
|
createTestZone(t, db)
|
|
|
|
record, err := db.CreateRecord("svc.mcp.metacircular.net", "metacrypt", "A", "192.168.88.181", 300)
|
|
if err != nil {
|
|
t.Fatalf("create record: %v", err)
|
|
}
|
|
|
|
if err := db.DeleteRecord(record.ID); err != nil {
|
|
t.Fatalf("delete record: %v", err)
|
|
}
|
|
|
|
_, err = db.GetRecord(record.ID)
|
|
if !errors.Is(err, ErrNotFound) {
|
|
t.Fatalf("expected ErrNotFound after delete, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestDeleteRecordBumpsSerial(t *testing.T) {
|
|
db := openTestDB(t)
|
|
createTestZone(t, db)
|
|
|
|
record, err := db.CreateRecord("svc.mcp.metacircular.net", "metacrypt", "A", "192.168.88.181", 300)
|
|
if err != nil {
|
|
t.Fatalf("create record: %v", err)
|
|
}
|
|
|
|
zone, err := db.GetZone("svc.mcp.metacircular.net")
|
|
if err != nil {
|
|
t.Fatalf("get zone: %v", err)
|
|
}
|
|
serialBefore := zone.Serial
|
|
|
|
if err := db.DeleteRecord(record.ID); err != nil {
|
|
t.Fatalf("delete record: %v", err)
|
|
}
|
|
|
|
zone, err = db.GetZone("svc.mcp.metacircular.net")
|
|
if err != nil {
|
|
t.Fatalf("get zone after delete: %v", err)
|
|
}
|
|
if zone.Serial <= serialBefore {
|
|
t.Fatalf("serial should have bumped: %d <= %d", zone.Serial, serialBefore)
|
|
}
|
|
}
|
|
|
|
func TestLookupRecords(t *testing.T) {
|
|
db := openTestDB(t)
|
|
createTestZone(t, db)
|
|
|
|
_, err := db.CreateRecord("svc.mcp.metacircular.net", "metacrypt", "A", "192.168.88.181", 300)
|
|
if err != nil {
|
|
t.Fatalf("create record: %v", err)
|
|
}
|
|
|
|
records, err := db.LookupRecords("svc.mcp.metacircular.net", "metacrypt", "A")
|
|
if err != nil {
|
|
t.Fatalf("lookup records: %v", err)
|
|
}
|
|
if len(records) != 1 {
|
|
t.Fatalf("got %d records, want 1", len(records))
|
|
}
|
|
if records[0].Value != "192.168.88.181" {
|
|
t.Fatalf("got value %q, want %q", records[0].Value, "192.168.88.181")
|
|
}
|
|
}
|
|
|
|
func TestCreateRecordCNAMEMissingDot(t *testing.T) {
|
|
db := openTestDB(t)
|
|
createTestZone(t, db)
|
|
|
|
_, err := db.CreateRecord("svc.mcp.metacircular.net", "alias", "CNAME", "rift.mcp.metacircular.net", 300)
|
|
if err == nil {
|
|
t.Fatal("expected error for CNAME without trailing dot")
|
|
}
|
|
}
|
|
|
|
func TestMultipleARecords(t *testing.T) {
|
|
db := openTestDB(t)
|
|
createTestZone(t, db)
|
|
|
|
_, err := db.CreateRecord("svc.mcp.metacircular.net", "metacrypt", "A", "192.168.88.181", 300)
|
|
if err != nil {
|
|
t.Fatalf("create first A record: %v", err)
|
|
}
|
|
_, err = db.CreateRecord("svc.mcp.metacircular.net", "metacrypt", "A", "100.95.252.120", 300)
|
|
if err != nil {
|
|
t.Fatalf("create second A record: %v", err)
|
|
}
|
|
|
|
records, err := db.LookupRecords("svc.mcp.metacircular.net", "metacrypt", "A")
|
|
if err != nil {
|
|
t.Fatalf("lookup records: %v", err)
|
|
}
|
|
if len(records) != 2 {
|
|
t.Fatalf("got %d records, want 2", len(records))
|
|
}
|
|
}
|