mirror of
https://github.com/jakejarvis/rdapper.git
synced 2026-09-23 00:15:31 -04:00
- Parse the top-level RDAP `redacted` array (RFC 9537) into a new `redactions` field on `DomainRecord`; set `privacyEnabled` when any redaction references "registrant" - Expose `street`, `city`, `state`, `postalCode`, `country`, and `countryCode` on both `RegistrarInfo` and `Contact`; extract ISO 3166-1 alpha-2 from the vCard `cc` parameter (RFC 8605) - Split `tel` entries by fax type parameter (`"fax"` / `["work","fax"]`) into separate `tel` and `fax` lists; collect multiple email addresses as an array, collapsing singletons to a plain string - Prefer an exact `registration` event match over `reregistration` when extracting `creationDate` (RFC 9083) - Merge `redacted` arrays in `mergeRdapDocs`, deduplicating by JSON identity - Split `adr` street on `\r?\n` only (not commas) to avoid splitting inline comma-separated addresses
270 lines
7.3 KiB
TypeScript
270 lines
7.3 KiB
TypeScript
import { expect, test } from "vitest";
|
|
import { normalizeRdap } from "./normalize";
|
|
|
|
test("normalizeRdap maps registrar, contacts, nameservers, events, dnssec", () => {
|
|
const rdap = {
|
|
ldhName: "example.com",
|
|
unicodeName: "example.com",
|
|
entities: [
|
|
{
|
|
roles: ["registrar"],
|
|
vcardArray: [
|
|
"vcard",
|
|
[
|
|
["fn", {}, "text", "Registrar LLC"],
|
|
["email", {}, "text", "support@registrar.test"],
|
|
["tel", {}, "text", "+1.5555555555"],
|
|
["url", {}, "text", "https://registrar.example"],
|
|
],
|
|
],
|
|
publicIds: [{ type: "IANA Registrar ID", identifier: "9999" }],
|
|
},
|
|
{
|
|
roles: ["registrant"],
|
|
vcardArray: [
|
|
"vcard",
|
|
[
|
|
["fn", {}, "text", "Alice Registrant"],
|
|
["email", {}, "text", "alice@example.com"],
|
|
],
|
|
],
|
|
},
|
|
{
|
|
roles: ["administrative"],
|
|
vcardArray: ["vcard", [["fn", {}, "text", "Bob Admin"]]],
|
|
},
|
|
{
|
|
roles: ["technical"],
|
|
vcardArray: ["vcard", [["fn", {}, "text", "Carol Tech"]]],
|
|
},
|
|
],
|
|
nameservers: [
|
|
{
|
|
ldhName: "NS1.EXAMPLE.COM",
|
|
ipAddresses: { v4: ["192.0.2.1"], v6: ["2001:db8::1"] },
|
|
},
|
|
{ unicodeName: "ns2.example.com" },
|
|
],
|
|
secureDNS: {
|
|
delegationSigned: true,
|
|
dsData: [{ keyTag: 12345, algorithm: 13, digestType: 2, digest: "ABCDEF" }],
|
|
},
|
|
events: [
|
|
{ eventAction: "registration", eventDate: "2020-01-02T03:04:05Z" },
|
|
{ eventAction: "last changed", eventDate: "2021-01-02T03:04:05Z" },
|
|
{ eventAction: "expiration", eventDate: "2030-01-02T03:04:05Z" },
|
|
],
|
|
status: ["clientTransferProhibited"],
|
|
port43: "whois.example-registrar.test",
|
|
};
|
|
const rec = normalizeRdap("example.com", "com", rdap, ["https://rdap.example/"]);
|
|
expect(rec.domain).toBe("example.com");
|
|
expect(rec.tld).toBe("com");
|
|
expect(rec.registrar?.name).toBe("Registrar LLC");
|
|
expect(rec.registrar?.ianaId).toBe("9999");
|
|
expect(rec.contacts && rec.contacts.length >= 3).toBe(true);
|
|
expect(rec.nameservers && rec.nameservers.length === 2).toBe(true);
|
|
expect(rec.nameservers).toBeDefined();
|
|
expect(rec.nameservers?.[0]?.host).toBe("ns1.example.com");
|
|
expect(rec.dnssec?.enabled).toBeTruthy();
|
|
expect(rec.creationDate).toBe("2020-01-02T03:04:05Z");
|
|
expect(rec.expirationDate).toBe("2030-01-02T03:04:05Z");
|
|
expect(rec.transferLock).toBe(true);
|
|
expect(rec.whoisServer).toBe("whois.example-registrar.test");
|
|
expect(rec.source).toBe("rdap");
|
|
});
|
|
|
|
test("normalizeRdap derives privacyEnabled from registrant keywords", () => {
|
|
const rdap = {
|
|
ldhName: "example.com",
|
|
unicodeName: "example.com",
|
|
entities: [
|
|
{
|
|
roles: ["registrant"],
|
|
vcardArray: [
|
|
"vcard",
|
|
[
|
|
["fn", {}, "text", "REDACTED FOR PRIVACY"],
|
|
["org", {}, "text", "Example Org"],
|
|
],
|
|
],
|
|
},
|
|
],
|
|
};
|
|
const rec = normalizeRdap("example.com", "com", rdap, ["https://rdap.example/"]);
|
|
expect(rec.privacyEnabled).toBe(true);
|
|
});
|
|
|
|
test("normalizeRdap detects transfer lock with spaced status", () => {
|
|
const rdap = {
|
|
ldhName: "example.com",
|
|
status: ["client transfer prohibited"],
|
|
};
|
|
const rec = normalizeRdap("example.com", "com", rdap, []);
|
|
expect(rec.transferLock).toBe(true);
|
|
});
|
|
|
|
test("normalizeRdap detects transfer lock with camelCase status", () => {
|
|
const rdap = {
|
|
ldhName: "example.com",
|
|
status: ["clientTransferProhibited"],
|
|
};
|
|
const rec = normalizeRdap("example.com", "com", rdap, []);
|
|
expect(rec.transferLock).toBe(true);
|
|
});
|
|
|
|
test("normalizeRdap treats release-pending statuses as not registered", () => {
|
|
const rec = normalizeRdap(
|
|
"iba.com.br",
|
|
"com.br",
|
|
{
|
|
ldhName: "iba.com.br",
|
|
status: ["pending release"],
|
|
},
|
|
[],
|
|
);
|
|
expect(rec.isRegistered).toBe(false);
|
|
const active = normalizeRdap(
|
|
"example.com",
|
|
"com",
|
|
{
|
|
ldhName: "example.com",
|
|
status: ["active"],
|
|
},
|
|
[],
|
|
);
|
|
expect(active.isRegistered).toBe(true);
|
|
});
|
|
|
|
test("normalizeRdap reads vCard adr cc parameter into countryCode", () => {
|
|
const rec = normalizeRdap(
|
|
"example.com",
|
|
"com",
|
|
{
|
|
ldhName: "example.com",
|
|
entities: [
|
|
{
|
|
roles: ["registrant"],
|
|
vcardArray: [
|
|
"vcard",
|
|
[["adr", { cc: "us" }, "text", ["", "", "1 Main St", "Town", "CA", "90000", "USA"]]],
|
|
],
|
|
},
|
|
],
|
|
},
|
|
[],
|
|
);
|
|
expect(rec.contacts?.[0]?.country).toBe("USA");
|
|
expect(rec.contacts?.[0]?.countryCode).toBe("US");
|
|
});
|
|
|
|
test("normalizeRdap parses RFC 9537 redacted array and flags privacy", () => {
|
|
const rec = normalizeRdap(
|
|
"example.com",
|
|
"com",
|
|
{
|
|
ldhName: "example.com",
|
|
redacted: [
|
|
{
|
|
name: { description: "Registrant Email" },
|
|
prePath: "$.entities[?(@.roles[0]=='registrant')].vcardArray[1][?(@[0]=='email')]",
|
|
method: "emptyValue",
|
|
reason: { description: "Server policy" },
|
|
},
|
|
],
|
|
},
|
|
[],
|
|
);
|
|
expect(rec.redactions).toEqual([
|
|
expect.objectContaining({
|
|
name: "Registrant Email",
|
|
method: "emptyValue",
|
|
reason: "Server policy",
|
|
}),
|
|
]);
|
|
expect(rec.privacyEnabled).toBe(true);
|
|
});
|
|
|
|
test("normalizeRdap separates fax from tel and keeps multiple emails", () => {
|
|
const rec = normalizeRdap(
|
|
"example.com",
|
|
"com",
|
|
{
|
|
ldhName: "example.com",
|
|
entities: [
|
|
{
|
|
roles: ["registrant"],
|
|
vcardArray: [
|
|
"vcard",
|
|
[
|
|
["tel", { type: "voice" }, "text", "+1.111"],
|
|
["tel", { type: ["work", "fax"] }, "text", "+1.222"],
|
|
["email", {}, "text", "a@example.com"],
|
|
["email", {}, "text", "b@example.com"],
|
|
["adr", {}, "text", ["", "", "Suite 5, 1 Main St", "Town", "", "", ""]],
|
|
],
|
|
],
|
|
},
|
|
],
|
|
},
|
|
[],
|
|
);
|
|
const c = rec.contacts?.[0];
|
|
expect(c?.phone).toBe("+1.111");
|
|
expect(c?.fax).toBe("+1.222");
|
|
expect(c?.email).toEqual(["a@example.com", "b@example.com"]);
|
|
expect(c?.street).toEqual(["Suite 5, 1 Main St"]);
|
|
});
|
|
|
|
test("normalizeRdap prefers registration event over reregistration", () => {
|
|
const rec = normalizeRdap(
|
|
"example.com",
|
|
"com",
|
|
{
|
|
ldhName: "example.com",
|
|
events: [
|
|
{ eventAction: "reregistration", eventDate: "2024-01-01T00:00:00Z" },
|
|
{ eventAction: "registration", eventDate: "2020-01-01T00:00:00Z" },
|
|
],
|
|
},
|
|
[],
|
|
);
|
|
expect(rec.creationDate).toBe("2020-01-01T00:00:00Z");
|
|
});
|
|
|
|
test("normalizeRdap maps registrar adr and cc", () => {
|
|
const rec = normalizeRdap(
|
|
"example.com",
|
|
"com",
|
|
{
|
|
ldhName: "example.com",
|
|
entities: [
|
|
{
|
|
roles: ["registrar"],
|
|
vcardArray: [
|
|
"vcard",
|
|
[
|
|
["fn", {}, "text", "Registrar LLC"],
|
|
[
|
|
"adr",
|
|
{ cc: "CA" },
|
|
"text",
|
|
["", "", "5 King St", "Toronto", "ON", "M5H", "Canada"],
|
|
],
|
|
],
|
|
],
|
|
},
|
|
],
|
|
},
|
|
[],
|
|
);
|
|
expect(rec.registrar).toMatchObject({
|
|
street: ["5 King St"],
|
|
city: "Toronto",
|
|
state: "ON",
|
|
postalCode: "M5H",
|
|
country: "Canada",
|
|
countryCode: "CA",
|
|
});
|
|
});
|