20260723 新增 rpki-explorer 单容器部署资产并修复 parsed 投影渲染(#128):deploy/rpki-explorer 全套独立 compose 部署资产(对接 231 在跑 soak、retain 10 runs、SSH 转发访问);ProjectionView 按真实 API 结构解包 projection.object、证书走 resourceCertificate+extensions、CRL 读顶层字段并新增 IP/AS 资源格式化;e2e fixtures 对齐真实嵌套结构并新增证书/CRL parsed 覆盖
This commit is contained in:
@@ -209,7 +209,21 @@ export const OBJ_ROA_REJECTED = {
|
||||
rejectReason: "manifest stale: thisUpdate in the past",
|
||||
};
|
||||
|
||||
export const OBJECTS = [OBJ_ROA, OBJ_MFT, OBJ_CER_REJECTED, OBJ_ASPA, OBJ_ROA_REJECTED];
|
||||
export const OBJ_CRL = {
|
||||
objectInstanceId: "obj-crl-0001",
|
||||
uri: "rsync://repo-a.example/ca1/main.crl",
|
||||
sha256: SHA("f"),
|
||||
objectType: "crl",
|
||||
result: "ok",
|
||||
detailSummary: null,
|
||||
repoId: REPO_A.repoId,
|
||||
ppId: PP_1.ppId,
|
||||
sourceSection: "fresh",
|
||||
rejected: false,
|
||||
rejectReason: null,
|
||||
};
|
||||
|
||||
export const OBJECTS = [OBJ_ROA, OBJ_MFT, OBJ_CER_REJECTED, OBJ_ASPA, OBJ_ROA_REJECTED, OBJ_CRL];
|
||||
|
||||
export const STATS_VALIDATION = { ok: 1279, error: 2, skipped: 3 };
|
||||
export const STATS_OBJECT_TYPES = {
|
||||
@@ -226,45 +240,65 @@ export const STATS_REASONS = {
|
||||
};
|
||||
|
||||
export const ROA_PROJECTION = {
|
||||
schemaVersion: 1,
|
||||
schemaVersion: 2,
|
||||
sha256: SHA("a"),
|
||||
objectType: "roa",
|
||||
parseStatus: "ok",
|
||||
// Mirrors the live query service shape:
|
||||
// projection = { input, object: { type, roa|manifest|… }, schemaVersion, tool }
|
||||
projection: {
|
||||
type: "roa",
|
||||
roa: {
|
||||
asId: 65001,
|
||||
ipAddressFamilies: [
|
||||
{
|
||||
afi: "ipv4",
|
||||
addresses: [
|
||||
{ prefix: "203.0.113.0/24", maxLength: 24 },
|
||||
{ prefix: "198.51.100.0/25", maxLength: 25 },
|
||||
],
|
||||
},
|
||||
{
|
||||
afi: "ipv6",
|
||||
addresses: [{ prefix: "2001:db8::/32", maxLength: 48 }],
|
||||
},
|
||||
],
|
||||
input: {
|
||||
type: "roa",
|
||||
path: "rsync://repo-a.example/ca1/alice.roa",
|
||||
bytes: { len: 1280, sha256: SHA("a") },
|
||||
},
|
||||
object: {
|
||||
type: "roa",
|
||||
roa: {
|
||||
asId: 65001,
|
||||
ipAddressFamilies: [
|
||||
{
|
||||
afi: "ipv4",
|
||||
addresses: [
|
||||
{ prefix: "203.0.113.0/24", maxLength: 24 },
|
||||
{ prefix: "198.51.100.0/25", maxLength: 25 },
|
||||
],
|
||||
},
|
||||
{
|
||||
afi: "ipv6",
|
||||
addresses: [{ prefix: "2001:db8::/32", maxLength: 48 }],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
schemaVersion: 2,
|
||||
tool: "rpki",
|
||||
},
|
||||
};
|
||||
|
||||
export const MFT_PROJECTION = {
|
||||
schemaVersion: 1,
|
||||
schemaVersion: 2,
|
||||
sha256: SHA("b"),
|
||||
objectType: "mft",
|
||||
parseStatus: "ok",
|
||||
projection: {
|
||||
type: "mft",
|
||||
manifest: {
|
||||
manifestNumberHex: "0A2F",
|
||||
thisUpdate: "2026-07-16T23:00:00Z",
|
||||
nextUpdate: "2026-07-17T23:00:00Z",
|
||||
fileHashAlg: "sha256",
|
||||
fileCount: 3,
|
||||
input: {
|
||||
type: "mft",
|
||||
path: "rsync://repo-a.example/ca1/manifest.mft",
|
||||
bytes: { len: 2048, sha256: SHA("b") },
|
||||
},
|
||||
object: {
|
||||
type: "mft",
|
||||
manifest: {
|
||||
manifestNumberHex: "0A2F",
|
||||
thisUpdate: "2026-07-16T23:00:00Z",
|
||||
nextUpdate: "2026-07-17T23:00:00Z",
|
||||
fileHashAlg: "sha256",
|
||||
fileCount: 3,
|
||||
},
|
||||
},
|
||||
schemaVersion: 2,
|
||||
tool: "rpki",
|
||||
},
|
||||
};
|
||||
|
||||
@@ -274,6 +308,125 @@ export const MFT_FILES = [
|
||||
{ fileName: "stale.cer", hashHex: SHA("c") },
|
||||
];
|
||||
|
||||
// Certificate projection in the real nested API shape:
|
||||
// certificate payload under `object.resourceCertificate`, SKI/AKI/SIA/AIA/CRLDP
|
||||
// and IP/AS resources under `extensions`.
|
||||
export const CER_PROJECTION = {
|
||||
schemaVersion: 2,
|
||||
sha256: SHA("c"),
|
||||
objectType: "cer",
|
||||
parseStatus: "ok",
|
||||
projection: {
|
||||
input: {
|
||||
type: "cer",
|
||||
path: "rsync://repo-a.example/ca1/stale.cer",
|
||||
bytes: { len: 1534, sha256: SHA("c") },
|
||||
},
|
||||
object: {
|
||||
type: "cer",
|
||||
decode: { profileValid: true },
|
||||
resourceCertificate: {
|
||||
kind: "Ca",
|
||||
version: 3,
|
||||
serialNumberHex: "02C6C7",
|
||||
issuer: "CN=A90DC5BE, serialNumber=0E65A4F5",
|
||||
subject: "CN=A9114E750000, serialNumber=5A179648",
|
||||
validity: { notBefore: "2026-07-23T00:30:06Z", notAfter: "2027-09-30T00:00:00Z" },
|
||||
signatureAlgorithm: "1.2.840.113549.1.1.11",
|
||||
extensions: {
|
||||
subjectKeyIdentifier: "5a179648b3ef2369dce7bdb58140ff7dc7060abf",
|
||||
authorityKeyIdentifier: "0e65a4f5fd36b5bd68eb3c923408978c907aa79f",
|
||||
basicConstraintsCa: true,
|
||||
subjectInfoAccess: {
|
||||
accessDescriptions: [
|
||||
{ accessMethodOid: "1.3.6.1.5.5.7.48.5", accessLocation: "rsync://repo-a.example/ca1/" },
|
||||
],
|
||||
},
|
||||
caIssuersUris: ["rsync://repo-a.example/ca1/issuer.cer"],
|
||||
crlDistributionPointsUris: ["rsync://repo-a.example/ca1/main.crl"],
|
||||
ipResources: {
|
||||
families: [
|
||||
{
|
||||
afi: "Ipv4",
|
||||
choice: {
|
||||
AddressesOrRanges: [
|
||||
{ Prefix: { addr: [103, 152, 34, 0], afi: "Ipv4", prefix_len: 23 } },
|
||||
{ Range: { min: { addr: [192, 0, 2, 0] }, max: { addr: [192, 0, 2, 255] } } },
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
afi: "Ipv6",
|
||||
choice: {
|
||||
AddressesOrRanges: [
|
||||
{
|
||||
Prefix: {
|
||||
addr: [38, 6, 104, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
afi: "Ipv6",
|
||||
prefix_len: 32,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
asResources: {
|
||||
asnum: {
|
||||
AsIdsOrRanges: [{ Id: 38008 }, { Id: 38023 }, { Range: { min: 65536, max: 65551 } }],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
schemaVersion: 2,
|
||||
tool: "rpki",
|
||||
},
|
||||
};
|
||||
|
||||
// CRL projection in the real nested API shape: CRL fields sit directly on the
|
||||
// typed payload (no `crl` wrapper); crlNumber/AKI live under `extensions`.
|
||||
export const CRL_PROJECTION = {
|
||||
schemaVersion: 2,
|
||||
sha256: SHA("f"),
|
||||
objectType: "crl",
|
||||
parseStatus: "ok",
|
||||
projection: {
|
||||
input: {
|
||||
type: "crl",
|
||||
path: "rsync://repo-a.example/ca1/main.crl",
|
||||
bytes: { len: 900, sha256: SHA("f") },
|
||||
},
|
||||
object: {
|
||||
type: "crl",
|
||||
decode: { profileValid: true },
|
||||
issuer: "CN=A9114E750000, serialNumber=5A179648",
|
||||
thisUpdate: "2026-07-23T02:47:00Z",
|
||||
nextUpdate: "2026-07-24T02:52:00Z",
|
||||
signatureAlgorithm: "1.2.840.113549.1.1.11",
|
||||
extensions: {
|
||||
authorityKeyIdentifier: "5a179648b3ef2369dce7bdb58140ff7dc7060abf",
|
||||
crlNumber: 3394,
|
||||
crlNumberHex: "0D42",
|
||||
},
|
||||
revokedCertificates: {
|
||||
count: 2,
|
||||
entries: [
|
||||
{ serialNumberHex: "1B0B0AD86D78E77D1491B5069E6287D08EEA7C17", revocationDate: "2027-02-13T13:22:01Z" },
|
||||
{ serialNumberHex: "6A106263F88133615B7E8D3466C192CEC0904F2D", revocationDate: "2027-03-18T19:58:00Z" },
|
||||
],
|
||||
},
|
||||
},
|
||||
schemaVersion: 2,
|
||||
tool: "rpki",
|
||||
},
|
||||
};
|
||||
|
||||
export const CRL_REVOKED = [
|
||||
{ serialNumberHex: "1B0B0AD86D78E77D1491B5069E6287D08EEA7C17", revocationDate: "2027-02-13T13:22:01Z" },
|
||||
{ serialNumberHex: "6A106263F88133615B7E8D3466C192CEC0904F2D", revocationDate: "2027-03-18T19:58:00Z" },
|
||||
];
|
||||
|
||||
export const VALIDATION_SUMMARY = {
|
||||
finalStatus: "valid",
|
||||
auditResult: "ok",
|
||||
|
||||
@@ -5,7 +5,10 @@
|
||||
*/
|
||||
import type { Page, Route } from "@playwright/test";
|
||||
import {
|
||||
CER_PROJECTION,
|
||||
CHAIN_EDGES,
|
||||
CRL_PROJECTION,
|
||||
CRL_REVOKED,
|
||||
EXPLAIN_RECORD,
|
||||
HEALTH,
|
||||
MFT_FILES,
|
||||
@@ -303,6 +306,8 @@ export async function installMockApi(
|
||||
if (b === "parsed" && !c) {
|
||||
if (obj.objectType === "roa") return fulfillJson(route, ROA_PROJECTION);
|
||||
if (obj.objectType === "manifest") return fulfillJson(route, MFT_PROJECTION);
|
||||
if (obj.objectType === "certificate") return fulfillJson(route, CER_PROJECTION);
|
||||
if (obj.objectType === "crl") return fulfillJson(route, CRL_PROJECTION);
|
||||
return fulfillJson(route, null);
|
||||
}
|
||||
if (b === "parsed" && c === "manifest-files") {
|
||||
@@ -310,7 +315,8 @@ export async function installMockApi(
|
||||
return fulfillJson(route, items, next);
|
||||
}
|
||||
if (b === "parsed" && c === "revoked-certs") {
|
||||
return fulfillJson(route, [], null);
|
||||
const { page: items, next } = paginate(obj.objectType === "crl" ? CRL_REVOKED : [], url, 100);
|
||||
return fulfillJson(route, items, next);
|
||||
}
|
||||
if (b === "validation" && !c) return fulfillJson(route, VALIDATION_SUMMARY);
|
||||
if (b === "validation" && c === "explain" && request.method() === "POST") {
|
||||
|
||||
37
ui/rpki-explorer/tests/e2e/projection-cert-crl.spec.ts
Normal file
37
ui/rpki-explorer/tests/e2e/projection-cert-crl.spec.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
import { installMockApi } from "./mockApi";
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await installMockApi(page);
|
||||
});
|
||||
|
||||
test("certificate object renders the resourceCertificate projection", async ({ page }) => {
|
||||
await page.goto("/objects/obj-cer-0001");
|
||||
|
||||
await expect(page.getByText("Serial number")).toBeVisible();
|
||||
await expect(page.getByText("02C6C7")).toBeVisible();
|
||||
await expect(page.getByText("CN=A90DC5BE, serialNumber=0E65A4F5")).toBeVisible();
|
||||
await expect(page.getByText("CN=A9114E750000, serialNumber=5A179648")).toBeVisible();
|
||||
await expect(page.getByText("5a179648b3ef2369dce7bdb58140ff7dc7060abf").first()).toBeVisible();
|
||||
await expect(page.getByText("rsync://repo-a.example/ca1/main.crl")).toBeVisible();
|
||||
// IP resources rendered from the real serde shape (prefix + range, v4 + v6)
|
||||
await expect(page.getByText("103.152.34.0/23")).toBeVisible();
|
||||
await expect(page.getByText("192.0.2.0 - 192.0.2.255")).toBeVisible();
|
||||
await expect(page.getByText("2606:6800::/32")).toBeVisible();
|
||||
// AS resources rendered as AS chips (Id + Range)
|
||||
await expect(page.getByText("AS38008", { exact: true })).toBeVisible();
|
||||
await expect(page.getByText("AS65536-AS65551")).toBeVisible();
|
||||
await expect(page.getByText("1.2.840.113549.1.1.11")).toBeVisible();
|
||||
});
|
||||
|
||||
test("crl object renders header fields from the top-level payload", async ({ page }) => {
|
||||
await page.goto("/objects/obj-crl-0001");
|
||||
|
||||
await expect(page.getByText("CN=A9114E750000, serialNumber=5A179648")).toBeVisible();
|
||||
await expect(page.getByText("CRL number")).toBeVisible();
|
||||
await expect(page.getByText("0D42")).toBeVisible();
|
||||
await expect(page.getByText("5a179648b3ef2369dce7bdb58140ff7dc7060abf")).toBeVisible();
|
||||
// Revoked certificates table from the sub-endpoint
|
||||
await expect(page.getByRole("cell", { name: "1B0B0AD86D78E77D1491B5069E6287D08EEA7C17" })).toBeVisible();
|
||||
await expect(page.getByRole("cell", { name: "6A106263F88133615B7E8D3466C192CEC0904F2D" })).toBeVisible();
|
||||
});
|
||||
Reference in New Issue
Block a user