mirror of
https://github.com/Xevion/rdap.git
synced 2025-12-06 19:16:03 -06:00
test: add comprehensive testing infrastructure with critical bug fixes
- Add Vitest testing framework with 88 passing tests across 4 test files - Fix critical entity validator bug (service array index) - Implement validator architecture with 'matched but invalid' state support - Add strict IPv4/IPv6 validation with detailed error messages - Add case-insensitive domain and ASN matching - Add explicit validator priority ordering (url→json→tld→ip→domain) - Add integration tests with real IANA registry validation - Add AutnumCard component for AS number display - Update dependencies: prettier 2.8.1→2.8.8 Test coverage: - helpers.test.ts: IPv4/IPv6 CIDR matching (27 tests) - helpers.asn.test.ts: ASN range validation (22 tests) - rdap.test.ts: Type detection with edge cases (36 tests) - rdap.integration.test.ts: Real IANA registry tests (3 tests) Bug fixes: - Entity validator now correctly uses service[1] for tags (0=email, 1=tags, 2=urls) - IPv4 validation rejects octets >255 with specific error messages - IPv6 validation checks for invalid hex chars and multiple :: - Domain regex supports multi-label domains (a.b.c.d.example.net) - Type detection priority prevents URL/JSON false matches as domains
This commit is contained in:
@@ -33,7 +33,7 @@ const AbstractCard: FunctionComponent<AbstractCardProps> = ({
|
||||
{url != undefined ? (
|
||||
<div className="pr-2">
|
||||
<a href={url} target="_blank" rel="noreferrer">
|
||||
<LinkIcon className="h-5 w-5 mt-1 cursor-pointer" />
|
||||
<LinkIcon className="mt-1 h-5 w-5 cursor-pointer" />
|
||||
</a>
|
||||
</div>
|
||||
) : null}
|
||||
@@ -43,14 +43,20 @@ const AbstractCard: FunctionComponent<AbstractCardProps> = ({
|
||||
<ClipboardDocumentIcon
|
||||
onClick={() => {
|
||||
// stringify the JSON object, then begin the async clipboard write
|
||||
navigator.clipboard.writeText(JSON.stringify(data, null, 4)).then(() => {
|
||||
console.log('Copied to clipboard.');
|
||||
}, (err) => {
|
||||
if (err instanceof Error)
|
||||
console.error(`Failed to copy to clipboard (${err.toString()}).`);
|
||||
else
|
||||
console.error("Failed to copy to clipboard.");
|
||||
});
|
||||
navigator.clipboard
|
||||
.writeText(JSON.stringify(data, null, 4))
|
||||
.then(
|
||||
() => {
|
||||
console.log("Copied to clipboard.");
|
||||
},
|
||||
(err) => {
|
||||
if (err instanceof Error)
|
||||
console.error(
|
||||
`Failed to copy to clipboard (${err.toString()}).`
|
||||
);
|
||||
else console.error("Failed to copy to clipboard.");
|
||||
}
|
||||
);
|
||||
}}
|
||||
className="h-6 w-6 cursor-pointer"
|
||||
/>
|
||||
@@ -80,7 +86,7 @@ const AbstractCard: FunctionComponent<AbstractCardProps> = ({
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
<div className="max-w-full p-2 px-4 overflow-x-auto">
|
||||
<div className="max-w-full overflow-x-auto p-2 px-4">
|
||||
{showRaw ? (
|
||||
<pre className="scrollbar-thin m-2 max-h-[40rem] max-w-full overflow-y-auto whitespace-pre-wrap rounded">
|
||||
{JSON.stringify(data, null, 4)}
|
||||
|
||||
@@ -23,9 +23,11 @@ const DynamicDate: FunctionComponent<DynamicDateProps> = ({
|
||||
return (
|
||||
<button onClick={toggleFormat}>
|
||||
<span className="dashed" title={date.toISOString()}>
|
||||
{showAbsolute
|
||||
? format(date, absoluteFormat ?? "LLL do, y HH:mm:ss xxx")
|
||||
: <TimeAgo date={date} />}
|
||||
{showAbsolute ? (
|
||||
format(date, absoluteFormat ?? "LLL do, y HH:mm:ss xxx")
|
||||
) : (
|
||||
<TimeAgo date={date} />
|
||||
)}
|
||||
</span>
|
||||
</button>
|
||||
);
|
||||
|
||||
@@ -16,15 +16,22 @@ const ErrorCard: FunctionComponent<ErrorCardProps> = ({
|
||||
className,
|
||||
}) => {
|
||||
return (
|
||||
<div className={clsx(className, "rounded-md border border-red-700/30 bg-zinc-800 pt-3 px-3 pb-1")}>
|
||||
<div
|
||||
className={clsx(
|
||||
className,
|
||||
"rounded-md border border-red-700/30 bg-zinc-800 px-3 pb-1 pt-3"
|
||||
)}
|
||||
>
|
||||
<div className="flex">
|
||||
<div className="flex-shrink-0">
|
||||
<XCircleIcon className="h-5 w-5 text-red-300" aria-hidden="true" />
|
||||
</div>
|
||||
<div className="ml-3 text-sm text-red-300 w-full">
|
||||
<div className="ml-3 w-full text-sm text-red-300">
|
||||
<h3 className="font-medium text-red-200">{title}</h3>
|
||||
{description != undefined ? (
|
||||
<div className="mt-2 whitespace-pre-wrap max-h-24 overflow-y-auto w-full" >{description}</div>
|
||||
<div className="mt-2 max-h-24 w-full overflow-y-auto whitespace-pre-wrap">
|
||||
{description}
|
||||
</div>
|
||||
) : null}
|
||||
<div className="mt-2">
|
||||
{issues != undefined ? (
|
||||
|
||||
@@ -18,7 +18,7 @@ const Property: FunctionComponent<PropertyProps> = ({
|
||||
return (
|
||||
<>
|
||||
<dt className={clsx("font-medium", titleClass)}>{title}:</dt>
|
||||
<dd className={clsx("mt-2 mb-2 ml-6", valueClass)}>{children}</dd>
|
||||
<dd className={clsx("mb-2 ml-6 mt-2", valueClass)}>{children}</dd>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -8,7 +8,9 @@ const PropertyListItem: FunctionComponent<{
|
||||
}> = ({ title, children }) => {
|
||||
return (
|
||||
<li>
|
||||
<span className="dashed" title={title}>{children}</span>
|
||||
<span className="dashed" title={title}>
|
||||
{children}
|
||||
</span>
|
||||
</li>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -193,14 +193,20 @@ const LookupInput: FunctionComponent<LookupInputProps> = ({
|
||||
{selected == "auto" ? (
|
||||
// If the detected type was provided, then notate which in parentheses. Compact object naming might be better in the future.
|
||||
detectedType.isJust ? (
|
||||
<>Auto (<span className="animate-pulse">{targetShortNames[detectedType.value]}</span>)</>
|
||||
<>
|
||||
Auto (
|
||||
<span className="animate-pulse">
|
||||
{targetShortNames[detectedType.value]}
|
||||
</span>
|
||||
)
|
||||
</>
|
||||
) : (
|
||||
objectNames["auto"]
|
||||
)
|
||||
) : (
|
||||
<>
|
||||
<LockClosedIcon
|
||||
className="mr-2.5 mb-1 inline h-4 w-4 animate-pulse text-zinc-500"
|
||||
className="mb-1 mr-2.5 inline h-4 w-4 animate-pulse text-zinc-500"
|
||||
aria-hidden
|
||||
/>
|
||||
{objectNames[selected]}
|
||||
@@ -292,7 +298,7 @@ const LookupInput: FunctionComponent<LookupInputProps> = ({
|
||||
</div>
|
||||
</div>
|
||||
<div className="col">
|
||||
<div className="flex flex-wrap pt-3 pb-1 text-sm">
|
||||
<div className="flex flex-wrap pb-1 pt-3 text-sm">
|
||||
<div className="whitespace-nowrap">
|
||||
<input
|
||||
className="ml-2 mr-1 whitespace-nowrap text-zinc-800 accent-blue-700"
|
||||
|
||||
60
src/components/lookup/AutnumCard.tsx
Normal file
60
src/components/lookup/AutnumCard.tsx
Normal file
@@ -0,0 +1,60 @@
|
||||
import type { FunctionComponent } from "react";
|
||||
import React from "react";
|
||||
import type { AutonomousNumber } from "@/types";
|
||||
import Events from "@/components/lookup/Events";
|
||||
import Property from "@/components/common/Property";
|
||||
import PropertyList from "@/components/common/PropertyList";
|
||||
import AbstractCard from "@/components/common/AbstractCard";
|
||||
|
||||
export type AutnumCardProps = {
|
||||
data: AutonomousNumber;
|
||||
url?: string;
|
||||
};
|
||||
|
||||
const AutnumCard: FunctionComponent<AutnumCardProps> = ({
|
||||
data,
|
||||
url,
|
||||
}: AutnumCardProps) => {
|
||||
const asnRange =
|
||||
data.startAutnum === data.endAutnum
|
||||
? `AS${data.startAutnum}`
|
||||
: `AS${data.startAutnum}-AS${data.endAutnum}`;
|
||||
|
||||
return (
|
||||
<AbstractCard
|
||||
data={data}
|
||||
url={url}
|
||||
header={
|
||||
<>
|
||||
<span className="font-mono tracking-tighter">AUTONOMOUS SYSTEM</span>
|
||||
<span className="font-mono tracking-wide">{asnRange}</span>
|
||||
<span className="whitespace-nowrap">({data.handle})</span>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<dl>
|
||||
<Property title="Name">{data.name}</Property>
|
||||
<Property title="Handle">{data.handle}</Property>
|
||||
<Property title="ASN Range">
|
||||
{data.startAutnum === data.endAutnum
|
||||
? `AS${data.startAutnum}`
|
||||
: `AS${data.startAutnum} - AS${data.endAutnum}`}
|
||||
</Property>
|
||||
<Property title="Type">{data.type}</Property>
|
||||
<Property title="Country">{data.country.toUpperCase()}</Property>
|
||||
<Property title="Events">
|
||||
<Events key={0} data={data.events} />
|
||||
</Property>
|
||||
<PropertyList title="Status">
|
||||
{data.status.map((status, index) => (
|
||||
<PropertyList.Item key={index} title={status}>
|
||||
{status}
|
||||
</PropertyList.Item>
|
||||
))}
|
||||
</PropertyList>
|
||||
</dl>
|
||||
</AbstractCard>
|
||||
);
|
||||
};
|
||||
|
||||
export default AutnumCard;
|
||||
@@ -23,7 +23,9 @@ const DomainCard: FunctionComponent<DomainProps> = ({
|
||||
header={
|
||||
<>
|
||||
<span className="font-mono tracking-tighter">DOMAIN</span>
|
||||
<span className="font-mono tracking-wide">{data.ldhName ?? data.unicodeName}</span>
|
||||
<span className="font-mono tracking-wide">
|
||||
{data.ldhName ?? data.unicodeName}
|
||||
</span>
|
||||
<span className="whitespace-nowrap">({data.handle})</span>
|
||||
</>
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { FunctionComponent } from "react";
|
||||
import DomainCard from "@/components/lookup/DomainCard";
|
||||
import IPCard from "@/components/lookup/IPCard";
|
||||
import AutnumCard from "@/components/lookup/AutnumCard";
|
||||
import type {
|
||||
Domain,
|
||||
AutonomousNumber,
|
||||
@@ -22,13 +23,17 @@ export type ObjectProps = {
|
||||
url?: string;
|
||||
};
|
||||
|
||||
const Generic: FunctionComponent<ObjectProps> = ({ data, url }: ObjectProps) => {
|
||||
const Generic: FunctionComponent<ObjectProps> = ({
|
||||
data,
|
||||
url,
|
||||
}: ObjectProps) => {
|
||||
switch (data.objectClassName) {
|
||||
case "domain":
|
||||
return <DomainCard url={url} data={data} />;
|
||||
case "ip network":
|
||||
return <IPCard url={url} data={data} />;
|
||||
case "autnum":
|
||||
return <AutnumCard url={url} data={data} />;
|
||||
case "entity":
|
||||
case "nameserver":
|
||||
default:
|
||||
|
||||
@@ -11,10 +11,7 @@ export type IPCardProps = {
|
||||
url?: string;
|
||||
};
|
||||
|
||||
const IPCard: FunctionComponent<IPCardProps> = ({
|
||||
data,
|
||||
url,
|
||||
}: IPCardProps) => {
|
||||
const IPCard: FunctionComponent<IPCardProps> = ({ data, url }: IPCardProps) => {
|
||||
return (
|
||||
<AbstractCard
|
||||
data={data}
|
||||
@@ -37,9 +34,7 @@ const IPCard: FunctionComponent<IPCardProps> = ({
|
||||
<Property title="Start Address">{data.startAddress}</Property>
|
||||
<Property title="End Address">{data.endAddress}</Property>
|
||||
<Property title="Type">{data.type}</Property>
|
||||
{data.country && (
|
||||
<Property title="Country">{data.country}</Property>
|
||||
)}
|
||||
{data.country && <Property title="Country">{data.country}</Property>}
|
||||
{data.parentHandle && (
|
||||
<Property title="Parent Handle">{data.parentHandle}</Property>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user