Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(w3c/headers): validate header links #4037

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 36 additions & 17 deletions src/w3c/headers.js
Original file line number Diff line number Diff line change
Expand Up @@ -393,11 +393,22 @@ export async function run(conf) {
}

if (conf.isEd) conf.thisVersion = conf.edDraftURI;
if (conf.isCGBG) validateCGBG(conf);
if (conf.isCGBG) {
validateCGBG(conf);
}
if (conf.isTagEditorFinding && !conf.latestVersion) {
conf.latestVersion = null;
}
if (conf.latestVersion !== null) {
conf.latestVersion = conf.latestVersion
? w3Url(conf.latestVersion)
: w3Url(`${pubSpace}/${conf.shortName}/`);
const exists = await resourceExists(conf.latestVersion);
sidvishnoi marked this conversation as resolved.
Show resolved Hide resolved
if (!exists && conf.specStatus !== "FPWD") {
const msg = `The "Latest published version:" header link points to a URL that does not exist.`;
const hint = docLink`Check that the ${"[shortname]"} is correct and you are using the right ${"[specStatus]"} for this kind of document.`;
showWarning(msg, name, { hint });
}
}

if (conf.latestVersion) validateIfAllowedOnTR(conf);
Expand Down Expand Up @@ -474,7 +485,9 @@ export async function run(conf) {
conf.publishISODate = conf.publishDate.toISOString();
conf.shortISODate = ISODate.format(conf.publishDate);
validatePatentPolicies(conf);
await deriveHistoryURI(conf);

conf.historyURI = await deriveHistoryURI(conf);

if (conf.isTagEditorFinding) {
delete conf.thisVersion;
delete conf.latestVersion;
Expand Down Expand Up @@ -668,7 +681,6 @@ function validateIfAllowedOnTR(conf) {
const msg = docLink`Documents with a status of \`"${conf.specStatus}"\` can't be published on the W3C's /TR/ (Technical Report) space.`;
const hint = docLink`Ask a W3C Team Member for a W3C URL where the report can be published and change ${"[latestVersion]"} to something else.`;
showError(msg, name, { hint });
return;
}
}

Expand All @@ -694,18 +706,23 @@ function derivePubSpace(conf) {

function validateCGBG(conf) {
const reportType = status2text[conf.specStatus];
const latestVersionURL = conf.latestVersion
? new URL(w3Url(conf.latestVersion))
: null;

if (!conf.wg) {
const msg = docLink`The ${"[group]"} configuration option is required for this kind of document (${reportType}).`;
showError(msg, name);
return;
}

if (conf.specStatus.endsWith("-DRAFT") && !conf.latestVersion) {
conf.latestVersion = null;
return;
}

// Deal with final reports
if (conf.isCGFinal) {
const latestVersionURL = conf.latestVersion
? new URL(w3Url(conf.latestVersion))
: null;
// Final report require a w3.org URL.
const isW3C =
latestVersionURL?.origin === "https://www.w3.org" ||
Expand All @@ -720,18 +737,17 @@ function validateCGBG(conf) {
}

async function deriveHistoryURI(conf) {
if (!conf.shortName || conf.historyURI === null || !conf.latestVersion) {
return; // Nothing to do
if (!conf.shortName || !conf.latestVersion) {
return null;
}

const canShowHistory = conf.isEd || trStatus.includes(conf.specStatus);

if (conf.historyURI && !canShowHistory) {
const msg = docLink`The ${"[historyURI]"} can't be used with non /TR/ documents.`;
if (!canShowHistory && conf.historyURI) {
const msg = docLink`The ${"[historyURI]"} can't be used with non-standards track documents.`;
const hint = docLink`Please remove ${"[historyURI]"}.`;
showError(msg, name, { hint });
conf.historyURI = null;
return;
return conf.historyURI;
}

const historyURL = new URL(
Expand All @@ -753,13 +769,16 @@ async function deriveHistoryURI(conf) {
// Do a fetch HEAD request to see if the history exists...
// We don't discriminate... if it's on the W3C website with a history,
// we show it.
const exists = await resourceExists(historyURL);
return exists ? historyURL.href : null;
}

async function resourceExists(url) {
try {
const response = await fetch(historyURL, { method: "HEAD" });
if (response.ok) {
conf.historyURI = response.url;
}
const response = await fetch(url, { method: "HEAD" });
return response.ok;
} catch {
// Ignore fetch errors
return false;
}
}

Expand Down
4 changes: 2 additions & 2 deletions src/w3c/templates/cgbg-headers.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,10 @@ export default (conf, options) => {
>
</dd>`
: ""}
${"latestVersion" in conf // latestVersion can be falsy
${conf.latestVersion !== null
? html`<dt>${l10n.latest_published_version}</dt>
<dd>
${conf.latestVersion
${conf.latestVersion !== ""
Comment on lines +40 to +43
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When is it null, and when is it "" in particular?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When it's null, it shouldn't be included... but when it's ""... I need to check :(

? html`<a href="${conf.latestVersion}"
>${conf.latestVersion}</a
>`
Expand Down
55 changes: 35 additions & 20 deletions tests/spec/w3c/headers-spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,11 @@
makeDefaultBody,
makeRSDoc,
makeStandardOps,
warningFilters,
} from "../SpecHelper.js";

const headerErrors = errorFilters.filter("w3c/headers");
const headerWarnings = warningFilters.filter("w3c/headers");
const defaultErrors = errorFilters.filter("w3c/defaults");

const findContent = string => {
Expand Down Expand Up @@ -62,26 +64,27 @@
expect(exportedDoc.querySelector(".head details[open]")).toBeTruthy();
});

it("links to the 'kinds of documents' only for W3C documents", async () => {
const statuses = ["FPWD", "WD", "CR", "CRD", "PR", "REC", "NOTE"];
for (const specStatus of statuses) {
for (const specStatus of recTrackStatus) {
it(`links to the 'kinds of documents' only for W3C documents with status ${specStatus}`, async () => {
const doc = await makeRSDoc(
makeStandardOps({ specStatus, group: "webapps" })
);
const w3cLink = doc.querySelector(
`.head a[href='https://www.w3.org/standards/types#${specStatus}']`
);
expect(w3cLink).withContext(`specStatus: ${specStatus}`).toBeTruthy();
}
expect(w3cLink).toBeTruthy();
});
}

for (const specStatus of ["unofficial", "base"]) {
for (const specStatus of noTrackStatus) {
it(`doesn't link to the 'kinds of documents' for non-rec track ${specStatus}`, async () => {
const doc = await makeRSDoc(makeStandardOps({ specStatus }));
const w3cLink = doc.querySelector(
".head a[href='https://www.w3.org/standards/types#UD']"
);
expect(w3cLink).withContext(`specStatus: ${specStatus}`).toBeNull();
}
});
expect(w3cLink).toBeNull();
});
}

describe("prevRecShortname & prevRecURI", () => {
it("takes prevRecShortname and prevRecURI into account", async () => {
Expand Down Expand Up @@ -1437,6 +1440,21 @@
expect(latestVersionLink.textContent).toBe("https://www.w3.org/TR/foo/");
});

it("warns if latestVersion URL doesn't exist", async () => {
const ops = makeStandardOps({
shortName: "foo",
specStatus: "WD",
group: "webapps",
github: "w3c/respec",
});
const doc = await makeRSDoc(ops);
const warnings = headerWarnings(doc);
expect(warnings).toHaveSize(1);
expect(warnings[0].message).toContain(
`The "Latest published version:" header link points to a URL that does not exist`
);
});

it("allows skipping latest published version link in initial ED", async () => {
const ops = makeStandardOps({
specStatus: "ED",
Expand Down Expand Up @@ -1543,19 +1561,15 @@
);
});

for (const specStatus of cgbgStatus.filter(s => s.endsWith("-DRAFT"))) {
for (const specStatus of cgStatus.filter(s => s.endsWith("-DRAFT"))) {
it(`doesn't set latestVersion URL for ${specStatus} status`, async () => {
const ops = makeStandardOps({
shortName: "some-report",
specStatus,
group: "wicg",
});
const doc = await makeRSDoc(ops);
const terms = [...doc.querySelectorAll(".head dt")];
const latestVersion = terms.find(
el => el.textContent.trim() === "Latest published version:"
);
expect(latestVersion).toHaveSize(0);
expect(contains(doc, "dt", "Latest published version:")).toHaveSize(0);
});
}
for (const specStatus of noTrackStatus) {
Expand Down Expand Up @@ -1989,7 +2003,7 @@
{ specStatus: "BG-FINAL", group: "publishingbg" },
];
for (const { specStatus, group } of finalReportStatus) {
it("requires that the ${specStatus} latestVersion be a w3c URL", async () => {
it(`requires that the ${specStatus} latestVersion be a w3c URL`, async () => {
const ops = makeStandardOps({
specStatus,
group,
Expand Down Expand Up @@ -2486,7 +2500,7 @@
});
const doc = await makeRSDoc(ops);
const [history] = contains(doc, ".head dt", "History:");
expect(history).toBeTruthy();

Check failure on line 2503 in tests/spec/w3c/headers-spec.js

View workflow job for this annotation

GitHub Actions / Karma Unit Tests (FirefoxHeadless)

W3C — Headers History allows overriding the historyURI Expected undefined to be truthy.
expect(history.nextElementSibling).toBeTruthy();
const historyLink = history.nextElementSibling.querySelector("a");
expect(historyLink).toBeTruthy();
Expand All @@ -2501,7 +2515,7 @@
});
const doc = await makeRSDoc(ops);
const [history] = contains(doc, ".head dt", "History:");
expect(history).toBeTruthy();

Check failure on line 2518 in tests/spec/w3c/headers-spec.js

View workflow job for this annotation

GitHub Actions / Karma Unit Tests (FirefoxHeadless)

W3C — Headers History generates the history URL for FPWD without checking if it exists Expected undefined to be truthy.
expect(history.nextElementSibling).toBeTruthy();
const historyLink = history.nextElementSibling.querySelector("a");
expect(historyLink).toBeTruthy();
Expand Down Expand Up @@ -2550,6 +2564,7 @@
const ops = makeStandardOps({
shortName: "payment-request",
specStatus: "ED",
group: "payments",
});
const doc = await makeRSDoc(ops);
const [history] = contains(doc, ".head dt", "History:");
Expand All @@ -2562,8 +2577,8 @@
);
});

for (const specStatus of trStatus) {
it(`includes the history for "${specStatus}" rec-track status`, async () => {
for (const specStatus of recTrackStatus) {
it(`includes the history for rec-track "${specStatus}" docs`, async () => {
const shortName = `push-api`;
const ops = makeStandardOps({
shortName,
Expand All @@ -2572,12 +2587,12 @@
});
const doc = await makeRSDoc(ops);
const [history] = contains(doc, ".head dt", "History:");
expect(history).withContext(specStatus).toBeTruthy();

Check failure on line 2590 in tests/spec/w3c/headers-spec.js

View workflow job for this annotation

GitHub Actions / Karma Unit Tests (FirefoxHeadless)

W3C — Headers History includes the history for rec-track "FPWD" docs FPWD: Expected undefined to be truthy.
expect(history.nextElementSibling).withContext(specStatus).toBeTruthy();
const historyLink = history.nextElementSibling.querySelector("a");
expect(historyLink).toBeTruthy();
expect(historyLink).withContext(specStatus).toBeTruthy();
expect(historyLink.href).toBe(

Check failure on line 2594 in tests/spec/w3c/headers-spec.js

View workflow job for this annotation

GitHub Actions / Karma Unit Tests (FirefoxHeadless)

W3C — Headers History includes the history for rec-track "CR" docs Expected 'https://www.w3.org/standards/history/push-api/' to be 'https://www.w3.org/standards/history/push-api'.

Check failure on line 2594 in tests/spec/w3c/headers-spec.js

View workflow job for this annotation

GitHub Actions / Karma Unit Tests (FirefoxHeadless)

W3C — Headers History includes the history for rec-track "CRD" docs Expected 'https://www.w3.org/standards/history/push-api/' to be 'https://www.w3.org/standards/history/push-api'.

Check failure on line 2594 in tests/spec/w3c/headers-spec.js

View workflow job for this annotation

GitHub Actions / Karma Unit Tests (FirefoxHeadless)

W3C — Headers History includes the history for rec-track "REC" docs Expected 'https://www.w3.org/standards/history/push-api/' to be 'https://www.w3.org/standards/history/push-api'.

Check failure on line 2594 in tests/spec/w3c/headers-spec.js

View workflow job for this annotation

GitHub Actions / Karma Unit Tests (FirefoxHeadless)

W3C — Headers History includes the history for rec-track "PR" docs Expected 'https://www.w3.org/standards/history/push-api/' to be 'https://www.w3.org/standards/history/push-api'.

Check failure on line 2594 in tests/spec/w3c/headers-spec.js

View workflow job for this annotation

GitHub Actions / Karma Unit Tests (FirefoxHeadless)

W3C — Headers History includes the history for rec-track "RSCND" docs Expected 'https://www.w3.org/standards/history/push-api/' to be 'https://www.w3.org/standards/history/push-api'.

Check failure on line 2594 in tests/spec/w3c/headers-spec.js

View workflow job for this annotation

GitHub Actions / Karma Unit Tests (FirefoxHeadless)

W3C — Headers History includes the history for rec-track "DISC" docs Expected 'https://www.w3.org/standards/history/push-api/' to be 'https://www.w3.org/standards/history/push-api'.

Check failure on line 2594 in tests/spec/w3c/headers-spec.js

View workflow job for this annotation

GitHub Actions / Karma Unit Tests (FirefoxHeadless)

W3C — Headers History includes the history for rec-track "WD" docs Expected 'https://www.w3.org/standards/history/push-api/' to be 'https://www.w3.org/standards/history/push-api'.
`https://www.w3.org/standards/history/${shortName}/`
`https://www.w3.org/standards/history/${shortName}`
);
});
}
Expand Down
Loading