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)) } }