feat: 聚合 M3 实况状态工作区
聚合 /v1/m3/status 的 M3 实况读入口,右侧硬件工作区显示 IO、operation、trace、audit 与 runtime durable blocker;覆盖后端契约与前端 fixture 验证。
This commit is contained in:
@@ -172,23 +172,33 @@ export async function describeM3StatusLive(options = {}) {
|
||||
};
|
||||
|
||||
if (!config.enabled) {
|
||||
const blocker = {
|
||||
code: "m3_control_disabled",
|
||||
layer: "cloud-api",
|
||||
zh: "M3 状态聚合未启用;前端不能绕过 cloud-api 直连 gateway/box-simu。"
|
||||
};
|
||||
const gateways = defaultM3Gateways({ sourceKind: "UNVERIFIED", observedAt });
|
||||
const boxes = defaultM3Boxes({ sourceKind: "UNVERIFIED", observedAt });
|
||||
const patchPanel = defaultM3PatchPanel({ observedAt });
|
||||
const blockedTrust = {
|
||||
...trust,
|
||||
blocker: "m3_control_disabled"
|
||||
};
|
||||
return {
|
||||
...base,
|
||||
status: "blocked",
|
||||
sourceKind: "BLOCKED",
|
||||
summary: "M3 状态聚合未启用;前端不能绕过 cloud-api 直连 gateway/box-simu。",
|
||||
gateways: defaultM3Gateways({ sourceKind: "UNVERIFIED", observedAt }),
|
||||
boxes: defaultM3Boxes({ sourceKind: "UNVERIFIED", observedAt }),
|
||||
patchPanel: defaultM3PatchPanel({ observedAt }),
|
||||
trust: {
|
||||
...trust,
|
||||
blocker: "m3_control_disabled"
|
||||
},
|
||||
blocker: {
|
||||
code: "m3_control_disabled",
|
||||
layer: "cloud-api",
|
||||
zh: "M3 状态聚合未启用;前端不能绕过 cloud-api 直连 gateway/box-simu。"
|
||||
}
|
||||
gateways,
|
||||
boxes,
|
||||
patchPanel,
|
||||
io: m3StatusIoSummary({ boxes, patchPanel, hardwareLive: false, observedAt }),
|
||||
operation: m3StatusOperationSummary(blockedTrust),
|
||||
trace: m3StatusTraceSummary(blockedTrust),
|
||||
audit: m3StatusAuditSummary(blockedTrust),
|
||||
runtimeDurable: m3StatusRuntimeDurableSummary(blockedTrust),
|
||||
trust: blockedTrust,
|
||||
blocker
|
||||
};
|
||||
}
|
||||
|
||||
@@ -342,6 +352,11 @@ export async function describeM3StatusLive(options = {}) {
|
||||
? blockers.some((blocker) => blocker.code === M3_IO_BLOCKER_CODES.dispatchFailed || blocker.code === M3_IO_BLOCKER_CODES.patchPanelUnavailable) ? "error" : "blocked"
|
||||
: trustGreen ? "live" : hardwareLive ? "blocked" : "unverified";
|
||||
const sourceKind = hardwareLive ? "DEV-LIVE" : "UNVERIFIED";
|
||||
const blocker = blockers[0] ?? (trust.blocker ? {
|
||||
code: trust.blocker,
|
||||
layer: "runtime-durable",
|
||||
zh: `runtime durable 仍 blocked:${trust.blocker}`
|
||||
} : null);
|
||||
|
||||
return {
|
||||
...base,
|
||||
@@ -355,12 +370,13 @@ export async function describeM3StatusLive(options = {}) {
|
||||
gateways,
|
||||
boxes,
|
||||
patchPanel,
|
||||
io: m3StatusIoSummary({ boxes, patchPanel, hardwareLive, observedAt }),
|
||||
operation: m3StatusOperationSummary(trust),
|
||||
trace: m3StatusTraceSummary(trust),
|
||||
audit: m3StatusAuditSummary(trust),
|
||||
runtimeDurable: m3StatusRuntimeDurableSummary(trust),
|
||||
trust,
|
||||
blocker: blockers[0] ?? (trust.blocker ? {
|
||||
code: trust.blocker,
|
||||
layer: "runtime-durable",
|
||||
zh: `runtime durable 仍 blocked:${trust.blocker}`
|
||||
} : null)
|
||||
blocker
|
||||
};
|
||||
}
|
||||
|
||||
@@ -759,6 +775,105 @@ async function buildM3StatusTrust({ runtimeStore, runtime }) {
|
||||
};
|
||||
}
|
||||
|
||||
function m3StatusIoSummary({ boxes, patchPanel, hardwareLive, observedAt }) {
|
||||
const sourceBox = boxes.find((box) => box.resourceId === M3_IO_CHAIN.sourceResourceId) ?? null;
|
||||
const targetBox = boxes.find((box) => box.resourceId === M3_IO_CHAIN.targetResourceId) ?? null;
|
||||
const do1 = sourceBox?.ports?.[M3_IO_CHAIN.sourcePort] ?? null;
|
||||
const di1 = targetBox?.ports?.[M3_IO_CHAIN.targetPort] ?? null;
|
||||
const blocker = !sourceBox?.online
|
||||
? M3_IO_BLOCKER_CODES.boxUnavailable
|
||||
: !targetBox?.online
|
||||
? M3_IO_BLOCKER_CODES.boxUnavailable
|
||||
: patchPanel?.connectionActive !== true
|
||||
? M3_IO_BLOCKER_CODES.wiringMissing
|
||||
: do1?.sourceKind !== "DEV-LIVE" || di1?.sourceKind !== "DEV-LIVE"
|
||||
? "m3_io_unverified"
|
||||
: null;
|
||||
return {
|
||||
status: hardwareLive ? "live" : blocker ? "blocked" : "unverified",
|
||||
sourceKind: hardwareLive ? "DEV-LIVE" : "UNVERIFIED",
|
||||
observedAt,
|
||||
chain: {
|
||||
from: `${M3_IO_CHAIN.sourceResourceId}:${M3_IO_CHAIN.sourcePort}`,
|
||||
via: M3_IO_CHAIN.patchPanelServiceId,
|
||||
to: `${M3_IO_CHAIN.targetResourceId}:${M3_IO_CHAIN.targetPort}`
|
||||
},
|
||||
do1: m3StatusPortSummary(sourceBox, M3_IO_CHAIN.sourcePort, do1),
|
||||
di1: m3StatusPortSummary(targetBox, M3_IO_CHAIN.targetPort, di1),
|
||||
patchPanel: {
|
||||
serviceId: patchPanel?.serviceId ?? M3_IO_CHAIN.patchPanelServiceId,
|
||||
connectionActive: patchPanel?.connectionActive === true,
|
||||
sourceKind: patchPanel?.sourceKind ?? "UNVERIFIED",
|
||||
observedAt: patchPanel?.lastSyncAt ?? observedAt,
|
||||
blocker: patchPanel?.connectionActive === true ? null : patchPanel?.lastError ?? M3_IO_BLOCKER_CODES.wiringMissing
|
||||
},
|
||||
blocker
|
||||
};
|
||||
}
|
||||
|
||||
function m3StatusPortSummary(box, port, state) {
|
||||
return {
|
||||
resourceId: box?.resourceId ?? null,
|
||||
boxId: box?.id ?? null,
|
||||
gatewayId: box?.gatewayId ?? null,
|
||||
gatewaySessionId: box?.gatewaySessionId ?? null,
|
||||
port,
|
||||
direction: state?.direction ?? null,
|
||||
value: state?.value ?? null,
|
||||
source: state?.source ?? "unverified",
|
||||
sourceKind: state?.sourceKind ?? "UNVERIFIED",
|
||||
observedAt: state?.observedAt ?? box?.observedAt ?? null,
|
||||
blocker: state?.lastError ?? (box?.online ? null : M3_IO_BLOCKER_CODES.boxUnavailable)
|
||||
};
|
||||
}
|
||||
|
||||
function m3StatusOperationSummary(trust) {
|
||||
return {
|
||||
status: trust.operationId ? "read" : "blocked",
|
||||
operationId: trust.operationId ?? null,
|
||||
source: trust.operationId ? "runtime-store audit/evidence" : "runtime-store",
|
||||
blocker: trust.operationId ? null : trust.blocker ?? "operation_not_persisted",
|
||||
readStatus: {
|
||||
audit: trust.readStatus?.audit ?? "blocked",
|
||||
evidence: trust.readStatus?.evidence ?? "blocked"
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function m3StatusTraceSummary(trust) {
|
||||
return {
|
||||
status: trust.traceId ? "read" : "blocked",
|
||||
traceId: trust.traceId ?? null,
|
||||
source: trust.traceId ? "runtime-store audit/evidence" : "runtime-store",
|
||||
blocker: trust.traceId ? null : trust.blocker ?? "trace_not_persisted"
|
||||
};
|
||||
}
|
||||
|
||||
function m3StatusAuditSummary(trust) {
|
||||
return {
|
||||
status: trust.auditId ? "read" : "blocked",
|
||||
auditId: trust.auditId ?? null,
|
||||
readStatus: trust.readStatus?.audit ?? "blocked",
|
||||
source: trust.auditId ? "runtime-store audit.event.query" : "runtime-store",
|
||||
blocker: trust.auditId ? null : trust.readStatus?.auditError ?? trust.blocker ?? "audit_not_persisted"
|
||||
};
|
||||
}
|
||||
|
||||
function m3StatusRuntimeDurableSummary(trust) {
|
||||
return {
|
||||
status: trust.durableStatus === "green" ? "green" : "blocked",
|
||||
green: trust.durableStatus === "green",
|
||||
blocker: trust.blocker ?? null,
|
||||
runtime: trust.runtime,
|
||||
readStatus: trust.readStatus ?? {
|
||||
audit: "blocked",
|
||||
evidence: "blocked"
|
||||
},
|
||||
auditReady: trust.readStatus?.audit === "read",
|
||||
evidenceReady: trust.readStatus?.evidence === "read"
|
||||
};
|
||||
}
|
||||
|
||||
async function safeRuntimeRead(read) {
|
||||
if (typeof read !== "function") {
|
||||
return {
|
||||
|
||||
@@ -206,6 +206,30 @@ test("M3 status live descriptor aggregates gateways, boxes, patch-panel, IO valu
|
||||
assert.equal(status.patchPanel.connectionActive, true);
|
||||
assert.equal(status.patchPanel.wiringConfigId, "wir_m3_do1_di1");
|
||||
assert.equal(status.patchPanel.lastError, null);
|
||||
assert.equal(status.io.status, "live");
|
||||
assert.equal(status.io.sourceKind, "DEV-LIVE");
|
||||
assert.equal(status.io.do1.resourceId, "res_boxsimu_1");
|
||||
assert.equal(status.io.do1.port, "DO1");
|
||||
assert.equal(status.io.do1.value, true);
|
||||
assert.equal(status.io.do1.sourceKind, "DEV-LIVE");
|
||||
assert.equal(status.io.di1.resourceId, "res_boxsimu_2");
|
||||
assert.equal(status.io.di1.port, "DI1");
|
||||
assert.equal(status.io.di1.value, true);
|
||||
assert.equal(status.io.di1.sourceKind, "DEV-LIVE");
|
||||
assert.equal(status.io.patchPanel.serviceId, "hwlab-patch-panel");
|
||||
assert.equal(status.io.patchPanel.connectionActive, true);
|
||||
assert.equal(status.operation.status, "blocked");
|
||||
assert.equal(status.operation.operationId, null);
|
||||
assert.equal(status.operation.blocker, "runtime_durable_adapter_auth_blocked");
|
||||
assert.equal(status.trace.status, "blocked");
|
||||
assert.equal(status.trace.traceId, null);
|
||||
assert.equal(status.trace.blocker, "runtime_durable_adapter_auth_blocked");
|
||||
assert.equal(status.audit.status, "blocked");
|
||||
assert.equal(status.audit.auditId, null);
|
||||
assert.equal(status.audit.readStatus, "blocked");
|
||||
assert.equal(status.runtimeDurable.status, "blocked");
|
||||
assert.equal(status.runtimeDurable.green, false);
|
||||
assert.equal(status.runtimeDurable.blocker, "runtime_durable_adapter_auth_blocked");
|
||||
assert.equal(status.trust.durableStatus, "blocked");
|
||||
assert.equal(status.trust.blocker, "runtime_durable_adapter_auth_blocked");
|
||||
assert.equal(status.trust.runtime.blocker, "runtime_durable_adapter_auth_blocked");
|
||||
@@ -249,6 +273,20 @@ test("M3 status clears nested runtime blocker when trusted durable evidence is g
|
||||
assert.equal(status.trust.traceId, "trc_hotfix_307_verify_1779527747043_read_false");
|
||||
assert.equal(status.trust.auditId, "aud_m3_di_read_d5afd84b-3a9d-475f-894f-dff8ea5f49ff_succeeded");
|
||||
assert.equal(status.trust.evidenceId, "evd_m3_di_read_75fc664f-e94f-41e8-816e-aff9fdb3666f_succeeded");
|
||||
assert.equal(status.operation.status, "read");
|
||||
assert.equal(status.operation.operationId, "op_m3_di_read_75fc664f-e94f-41e8-816e-aff9fdb3666f");
|
||||
assert.equal(status.operation.blocker, null);
|
||||
assert.equal(status.trace.status, "read");
|
||||
assert.equal(status.trace.traceId, "trc_hotfix_307_verify_1779527747043_read_false");
|
||||
assert.equal(status.trace.blocker, null);
|
||||
assert.equal(status.audit.status, "read");
|
||||
assert.equal(status.audit.auditId, "aud_m3_di_read_d5afd84b-3a9d-475f-894f-dff8ea5f49ff_succeeded");
|
||||
assert.equal(status.audit.blocker, null);
|
||||
assert.equal(status.runtimeDurable.status, "green");
|
||||
assert.equal(status.runtimeDurable.green, true);
|
||||
assert.equal(status.runtimeDurable.blocker, null);
|
||||
assert.equal(status.runtimeDurable.auditReady, true);
|
||||
assert.equal(status.runtimeDurable.evidenceReady, true);
|
||||
});
|
||||
|
||||
test("M3 status live descriptor keeps SOURCE fallback unverified when patch-panel route is missing", async () => {
|
||||
|
||||
@@ -3613,6 +3613,33 @@ test("cloud api /v1 exposes M3 IO control contract without generic frontend hard
|
||||
assert.equal(statusPayload.boxes.find((box) => box.resourceId === "res_boxsimu_1").ports.DO1.value, false);
|
||||
assert.equal(statusPayload.boxes.find((box) => box.resourceId === "res_boxsimu_2").ports.DI1.value, false);
|
||||
assert.equal(statusPayload.patchPanel.connectionActive, true);
|
||||
assert.equal(statusPayload.io.status, "live");
|
||||
assert.equal(statusPayload.io.do1.resourceId, "res_boxsimu_1");
|
||||
assert.equal(statusPayload.io.do1.port, "DO1");
|
||||
assert.equal(statusPayload.io.do1.value, false);
|
||||
assert.equal(statusPayload.io.do1.sourceKind, "DEV-LIVE");
|
||||
assert.equal(statusPayload.io.di1.resourceId, "res_boxsimu_2");
|
||||
assert.equal(statusPayload.io.di1.port, "DI1");
|
||||
assert.equal(statusPayload.io.di1.value, false);
|
||||
assert.equal(statusPayload.io.di1.sourceKind, "DEV-LIVE");
|
||||
assert.equal(statusPayload.io.patchPanel.serviceId, "hwlab-patch-panel");
|
||||
assert.equal(statusPayload.io.patchPanel.connectionActive, true);
|
||||
assert.equal(statusPayload.operation.status, "blocked");
|
||||
assert.equal(statusPayload.operation.operationId, null);
|
||||
assert.equal(statusPayload.operation.blocker, "runtime_durable_adapter_missing");
|
||||
assert.equal(statusPayload.trace.status, "blocked");
|
||||
assert.equal(statusPayload.trace.traceId, null);
|
||||
assert.equal(statusPayload.trace.blocker, "runtime_durable_adapter_missing");
|
||||
assert.equal(statusPayload.audit.status, "blocked");
|
||||
assert.equal(statusPayload.audit.auditId, null);
|
||||
assert.equal(statusPayload.audit.blocker, "runtime_durable_adapter_missing");
|
||||
assert.equal(statusPayload.runtimeDurable.status, "blocked");
|
||||
assert.equal(statusPayload.runtimeDurable.green, false);
|
||||
assert.equal(statusPayload.runtimeDurable.blocker, "runtime_durable_adapter_missing");
|
||||
assert.equal(statusPayload.runtimeDurable.readStatus.audit, "read");
|
||||
assert.equal(statusPayload.runtimeDurable.readStatus.evidence, "read");
|
||||
assert.equal(statusPayload.runtimeDurable.auditReady, true);
|
||||
assert.equal(statusPayload.runtimeDurable.evidenceReady, true);
|
||||
assert.equal(statusPayload.trust.durableStatus, "blocked");
|
||||
assert.notEqual(statusPayload.trust.durableStatus, "green");
|
||||
assert.equal(statusPayload.trust.runtime.blocker, "runtime_durable_adapter_missing");
|
||||
@@ -3655,6 +3682,20 @@ test("cloud api /v1/m3/status clears nested runtime blocker when trusted durable
|
||||
assert.equal(statusPayload.trust.traceId, "trc_hotfix_307_verify_1779527747043_read_false");
|
||||
assert.equal(statusPayload.trust.auditId, "aud_m3_di_read_d5afd84b-3a9d-475f-894f-dff8ea5f49ff_succeeded");
|
||||
assert.equal(statusPayload.trust.evidenceId, "evd_m3_di_read_75fc664f-e94f-41e8-816e-aff9fdb3666f_succeeded");
|
||||
assert.equal(statusPayload.operation.status, "read");
|
||||
assert.equal(statusPayload.operation.operationId, "op_m3_di_read_75fc664f-e94f-41e8-816e-aff9fdb3666f");
|
||||
assert.equal(statusPayload.operation.blocker, null);
|
||||
assert.equal(statusPayload.trace.status, "read");
|
||||
assert.equal(statusPayload.trace.traceId, "trc_hotfix_307_verify_1779527747043_read_false");
|
||||
assert.equal(statusPayload.trace.blocker, null);
|
||||
assert.equal(statusPayload.audit.status, "read");
|
||||
assert.equal(statusPayload.audit.auditId, "aud_m3_di_read_d5afd84b-3a9d-475f-894f-dff8ea5f49ff_succeeded");
|
||||
assert.equal(statusPayload.audit.blocker, null);
|
||||
assert.equal(statusPayload.runtimeDurable.status, "green");
|
||||
assert.equal(statusPayload.runtimeDurable.green, true);
|
||||
assert.equal(statusPayload.runtimeDurable.blocker, null);
|
||||
assert.equal(statusPayload.runtimeDurable.auditReady, true);
|
||||
assert.equal(statusPayload.runtimeDurable.evidenceReady, true);
|
||||
} finally {
|
||||
await new Promise((resolve, reject) => {
|
||||
server.close((error) => (error ? reject(error) : resolve()));
|
||||
|
||||
@@ -6374,7 +6374,7 @@ async function inspectM3StatusFixtureViewport(browser, url, viewport) {
|
||||
getComputedStyle(document.body).overflow === "hidden" &&
|
||||
document.documentElement.scrollHeight <= document.documentElement.clientHeight + 2 &&
|
||||
document.body.scrollHeight <= document.body.clientHeight + 2,
|
||||
keysVisible: ["状态", "Gateway 在线", "BOX 在线", "链路状态", "blocked", "DO1", "DI1", "runtime durable", "blocker"].every((term) => hardwareText.includes(term)),
|
||||
keysVisible: ["状态", "Gateway 在线", "BOX 在线", "链路状态", "blocked", "DO1", "DI1", "operation", "audit", "runtime durable", "blocker"].every((term) => hardwareText.includes(term)),
|
||||
tabsVisible: ["总览", "Gateway-SIMU", "BOX-SIMU-1", "BOX-SIMU-2", "Patch Panel"].every((term) => tabLabels.includes(term)),
|
||||
blockedVisible: text.includes("runtime_durable_adapter_auth_blocked") && !text.includes("实况可信"),
|
||||
noHorizontalOverflow:
|
||||
|
||||
@@ -1457,19 +1457,26 @@ function hardwareOverviewGroups(status) {
|
||||
const di1 = box2?.ports?.DI1;
|
||||
const patch = status.patchPanel ?? {};
|
||||
const trust = status.trust ?? {};
|
||||
const io = status.io ?? {};
|
||||
const operation = status.operation ?? {};
|
||||
const audit = status.audit ?? {};
|
||||
const runtimeDurable = status.runtimeDurable ?? {};
|
||||
const runtimeDurableStatus = runtimeDurable.status ?? trust.durableStatus ?? "blocked";
|
||||
return [
|
||||
hardwareKvGroup("总览", [
|
||||
["状态", hardwareStatusLabel(status), statusTone(status)],
|
||||
["观测时间", status.observedAt ?? "未验证", "source"],
|
||||
["Gateway 在线", `${onlineCount(status.gateways)} / ${status.gateways?.length ?? 0}`, onlineCount(status.gateways) === 2 ? "live" : "blocked"],
|
||||
["BOX 在线", `${onlineCount(status.boxes)} / ${status.boxes?.length ?? 0}`, onlineCount(status.boxes) === 2 ? "live" : "blocked"],
|
||||
["链路状态", m3LinkStatus(status, patch), m3LinkTone(status, patch)],
|
||||
["链路状态", io.status ?? m3LinkStatus(status, patch), m3LinkTone(status, patch)],
|
||||
["链路", `${M3_TRUSTED_ROUTE.fromResourceId}:${M3_TRUSTED_ROUTE.fromPort} -> ${M3_TRUSTED_ROUTE.patchPanelServiceId} -> ${M3_TRUSTED_ROUTE.toResourceId}:${M3_TRUSTED_ROUTE.toPort}`, patch.connectionActive ? "live" : "blocked"],
|
||||
["DO1", valueLabel(do1), portTone(do1)],
|
||||
["DI1", valueLabel(di1), portTone(di1)],
|
||||
["接线盘连接", String(Boolean(patch.connectionActive)), patch.connectionActive ? "live" : "blocked"],
|
||||
["runtime durable", trust.durableStatus ?? "blocked", trust.durableStatus === "green" ? "dev-live" : "blocked"],
|
||||
["blocker", trust.blocker ?? status.blocker?.code ?? "none", trust.blocker || status.blocker ? "blocked" : "source"]
|
||||
["operation", operation.operationId ?? operation.blocker ?? "未持久化", operation.operationId ? "source" : "blocked"],
|
||||
["audit", audit.auditId ?? audit.blocker ?? "未持久化", audit.auditId ? "source" : "blocked"],
|
||||
["runtime durable", runtimeDurableStatus, runtimeDurableStatus === "green" ? "dev-live" : "blocked"],
|
||||
["blocker", runtimeDurable.blocker ?? trust.blocker ?? status.blocker?.code ?? "none", runtimeDurable.blocker || trust.blocker || status.blocker ? "blocked" : "source"]
|
||||
]),
|
||||
hardwareKvGroup("可信证据", [
|
||||
["operationId", trust.operationId ?? "未持久化", trust.operationId ? "source" : "blocked"],
|
||||
|
||||
@@ -366,6 +366,14 @@ assert.match(functionBody(app, "sourceFallbackM3Status"), /unverified_cloud_api_
|
||||
assert.match(functionBody(app, "sourceFallbackM3Status"), /SOURCE 拓扑不能冒充实况硬件状态/);
|
||||
assert.doesNotMatch(functionBody(app, "renderHardwareStatus"), /hardwareRow\(\s*\{/u);
|
||||
assert.match(functionBody(app, "renderHardwareStatus"), /Key\/Value groups/u);
|
||||
assert.match(functionBody(app, "hardwareOverviewGroups"), /status\.io \?\? \{\}/);
|
||||
assert.match(functionBody(app, "hardwareOverviewGroups"), /status\.operation \?\? \{\}/);
|
||||
assert.match(functionBody(app, "hardwareOverviewGroups"), /status\.audit \?\? \{\}/);
|
||||
assert.match(functionBody(app, "hardwareOverviewGroups"), /status\.runtimeDurable \?\? \{\}/);
|
||||
assert.match(functionBody(app, "hardwareOverviewGroups"), /\["operation", operation\.operationId \?\? operation\.blocker/u);
|
||||
assert.match(functionBody(app, "hardwareOverviewGroups"), /\["audit", audit\.auditId \?\? audit\.blocker/u);
|
||||
assert.match(functionBody(app, "hardwareOverviewGroups"), /\["runtime durable", runtimeDurableStatus/u);
|
||||
assert.match(functionBody(app, "hardwareOverviewGroups"), /runtimeDurable\.blocker \?\? trust\.blocker/u);
|
||||
assert.doesNotMatch(app, /sourceKind:\s*counts\s*\?\s*"DEV-LIVE"/);
|
||||
assert.doesNotMatch(app, /counts\s*&&\s*(?:liveLink|sourceLink|m3Link)[\s\S]{0,80}\?\s*"DEV-LIVE"/);
|
||||
assert.match(app, /function renderRecords/);
|
||||
|
||||
Reference in New Issue
Block a user