View parity mismatches as diff (#98)

This commit is contained in:
Tobias Gesellchen
2026-03-06 21:26:24 +01:00
committed by GitHub
parent eb50e9b6f6
commit d2aaed0f9f
7 changed files with 93 additions and 18 deletions
+1
View File
@@ -3,5 +3,6 @@ certs/
default/
dns/
interactions/
parity_mismatches/
patterns.json
settings.json
@@ -131,4 +131,15 @@ func TestStaticWeb(t *testing.T) {
!strings.Contains(res.Header.Get("Content-Type"), "text/javascript") {
t.Errorf("JS: Expected javascript content type, got %s", res.Header.Get("Content-Type"))
}
// 3. Test diff.min.js
res, err = http.Get(ts.URL + "/web/js/diff.min.js")
if err != nil {
t.Fatal(err)
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
t.Errorf("Diff JS: Expected status OK, got %v", res.Status)
}
}
+5 -1
View File
@@ -295,6 +295,7 @@ func (s *Server) performMirror(r *http.Request) *mirrorResponseRecorder {
return recorder
}
// checkParity compares local response with upstream response.
func (s *Server) checkParity(req *http.Request, local, upstream *mirrorResponseRecorder) {
if local.status == 0 {
local.status = 200
@@ -324,7 +325,10 @@ func (s *Server) checkParity(req *http.Request, local, upstream *mirrorResponseR
}
// Basic body comparison (could be improved with XML semantic diff)
if !bytes.Equal(local.body.Bytes(), upstream.body.Bytes()) {
localBody := local.body.Bytes()
upstreamBody := upstream.body.Bytes()
if !bytes.Equal(localBody, upstreamBody) {
mismatch = true
reasons = append(reasons, "Body content mismatch")
+19 -1
View File
@@ -7,7 +7,25 @@ button { padding: 5px 10px; cursor: pointer; }
.summary-box { margin-top: 20px; padding: 15px; border: 1px solid #aaa; background-color: #f9f9f9; }
pre { background-color: #eee; padding: 10px; overflow-x: auto; font-size: 12px; }
.diff-container { display: flex; gap: 10px; }
.diff-pane { flex: 1; min-width: 0; }
.diff-pane pre {
background: #f8f9fa;
border: 1px solid #eee;
padding: 10px;
font-size: 0.85em;
overflow-x: auto;
white-space: pre-wrap;
word-break: break-all;
}
.diff-added {
background-color: #e6ffed !important;
text-decoration: none;
}
.diff-removed {
background-color: #ffeef0 !important;
text-decoration: line-through;
}
.config-header { font-weight: bold; margin-bottom: 5px; display: block; }
/* Tabs */
+1 -14
View File
@@ -5,6 +5,7 @@
<title>AfterTouch (SoundTouch Toolkit)</title>
<link rel="icon" href="/media/favicon-braille.svg" type="image/svg+xml"/>
<link rel="stylesheet" href="/web/css/style.css"/>
<script src="/web/js/diff.min.js"></script>
</head>
<body>
<h1>AfterTouch</h1>
@@ -1405,13 +1406,6 @@
></div>
<pre
id="diff-local-body"
style="
background: #f8f9fa;
border: 1px solid #eee;
padding: 10px;
font-size: 0.85em;
overflow-x: auto;
"
></pre>
</div>
<div class="diff-pane">
@@ -1430,13 +1424,6 @@
></div>
<pre
id="diff-upstream-body"
style="
background: #f8f9fa;
border: 1px solid #eee;
padding: 10px;
font-size: 0.85em;
overflow-x: auto;
"
></pre>
</div>
</div>
File diff suppressed because one or more lines are too long
+55 -2
View File
@@ -1021,8 +1021,39 @@ function viewParityMismatch(m) {
document.getElementById("diff-local-meta").innerText = `Status: ${m.local.status}`;
document.getElementById("diff-upstream-meta").innerText = `Status: ${m.upstream.status}`;
document.getElementById("diff-local-body").innerText = formatXML(m.local.body,);
document.getElementById("diff-upstream-body").innerText = formatXML(m.upstream.body,);
const localCT = (m.local.headers && m.local.headers["Content-Type"]) ? m.local.headers["Content-Type"][0] : "";
const upstreamCT = (m.upstream.headers && m.upstream.headers["Content-Type"]) ? m.upstream.headers["Content-Type"][0] : "";
const localBody = formatBody(m.local.body, localCT);
const upstreamBody = formatBody(m.upstream.body, upstreamCT);
if (typeof Diff !== 'undefined') {
const diff = Diff.diffChars(localBody, upstreamBody);
const localEl = document.getElementById("diff-local-body");
const upstreamEl = document.getElementById("diff-upstream-body");
localEl.innerHTML = "";
upstreamEl.innerHTML = "";
diff.forEach((part) => {
const span = document.createElement('span');
if (part.added) {
span.className = 'diff-added';
span.innerText = part.value;
upstreamEl.appendChild(span);
} else if (part.removed) {
span.className = 'diff-removed';
span.innerText = part.value;
localEl.appendChild(span);
} else {
localEl.appendChild(document.createTextNode(part.value));
upstreamEl.appendChild(document.createTextNode(part.value));
}
});
} else {
document.getElementById("diff-local-body").innerText = localBody;
document.getElementById("diff-upstream-body").innerText = upstreamBody;
}
document.getElementById("parity-diff-view").style.display = "block";
document
@@ -1030,6 +1061,28 @@ function viewParityMismatch(m) {
.scrollIntoView({behavior: "smooth"});
}
function formatBody(body, contentType) {
if (!body) return "";
contentType = (contentType || "").toLowerCase();
if (contentType.includes("json")) {
return formatJSON(body);
}
if (contentType.includes("xml")) {
return formatXML(body);
}
return body;
}
function formatJSON(json) {
if (!json) return "";
try {
const obj = typeof json === "string" ? JSON.parse(json) : json;
return JSON.stringify(obj, null, 2);
} catch (e) {
return json;
}
}
function formatXML(xml) {
if (!xml) return "";
try {