diff --git a/src/middleware/files.js b/src/middleware/files.js index 98cdd4fd..1f065bd9 100644 --- a/src/middleware/files.js +++ b/src/middleware/files.js @@ -42,6 +42,11 @@ module.exports = function () { const pathname = pathutils.normalizeMultiSlashes(parsedUrl.pathname); const search = parsedUrl.search || ""; + const parsedOriginalUrl = url.parse(req.originalUrl); + const originalPathname = pathutils.normalizeMultiSlashes( + parsedOriginalUrl.pathname + ); + const cleanUrlRules = !!_.get(req, "superstatic.cleanUrls"); // Exact file always wins. @@ -50,8 +55,11 @@ module.exports = function () { if (result) { // If we are using cleanURLs, we'll trim off any `.html` (or `/index.html`), if it exists. if (cleanUrlRules) { - if (_.endsWith(pathname, ".html")) { - let redirPath = pathutils.removeTrailingString(pathname, ".html"); + if (_.endsWith(originalPathname, ".html")) { + let redirPath = pathutils.removeTrailingString( + originalPathname, + ".html" + ); if (_.endsWith(redirPath, "/index")) { redirPath = pathutils.removeTrailingString(redirPath, "/index"); } @@ -67,7 +75,7 @@ module.exports = function () { } // Now, let's consider the trailing slash. - const hasTrailingSlash = pathutils.hasTrailingSlash(pathname); + const hasTrailingSlash = pathutils.hasTrailingSlash(originalPathname); // We want to check for some other files, namely an `index.html` if this were a directory. const pathAsDirectoryWithIndex = pathutils.asDirectoryIndex( @@ -83,7 +91,8 @@ module.exports = function () { !cleanUrlRules ) { return res.superstatic.handle({ - redirect: pathutils.addTrailingSlash(pathname) + search, + redirect: + pathutils.addTrailingSlash(originalPathname) + search, }); } if ( @@ -94,13 +103,14 @@ module.exports = function () { // No infinite redirects return res.superstatic.handle({ redirect: normalizeRedirectPath( - pathutils.removeTrailingSlash(pathname) + search + pathutils.removeTrailingSlash(originalPathname) + search ), }); } if (trailingSlashBehavior === true && !hasTrailingSlash) { return res.superstatic.handle({ - redirect: pathutils.addTrailingSlash(pathname) + search, + redirect: + pathutils.addTrailingSlash(originalPathname) + search, }); } // If we haven't returned yet, our path is "correct" and we should be serving a file, not redirecting. @@ -114,15 +124,20 @@ module.exports = function () { // We want to know if a specific mutation of the path exists. if (cleanUrlRules) { let appendedPath = pathname; + let appendedOriginalPath = originalPathname; if (hasTrailingSlash) { if (trailingSlashBehavior !== undefined) { // We want to remove the trailing slash and see if a file exists with an .html attached. appendedPath = - pathutils.removeTrailingString(pathname, "/") + ".html"; + pathutils.removeTrailingString(appendedPath, "/") + ".html"; + appendedOriginalPath = + pathutils.removeTrailingString(appendedOriginalPath, "/") + + ".html"; } } else { // Let's see if our path is a simple clean URL missing a .HTML5 appendedPath += ".html"; + appendedOriginalPath += ".html"; } return providerResult(req, res, appendedPath).then( @@ -134,7 +149,8 @@ module.exports = function () { // (This works because we are in the cleanURL block.) return res.superstatic.handle({ redirect: normalizeRedirectPath( - pathutils.removeTrailingSlash(pathname) + search + pathutils.removeTrailingSlash(originalPathname) + + search ), }); } @@ -148,17 +164,26 @@ module.exports = function () { appendedPath, "/index" ); + appendedOriginalPath = pathutils.removeTrailingString( + appendedOriginalPath, + ".html" + ); + appendedOriginalPath = pathutils.removeTrailingString( + appendedOriginalPath, + "/index" + ); return res.superstatic.handle({ redirect: - pathutils.addTrailingSlash(appendedPath) + search, + pathutils.addTrailingSlash(appendedOriginalPath) + + search, }); } // If we've gotten this far and still have `/index.html` on the end, we want to remove it from the URL. - if (_.endsWith(appendedPath, "/index.html")) { + if (_.endsWith(appendedOriginalPath, "/index.html")) { return res.superstatic.handle({ redirect: normalizeRedirectPath( pathutils.removeTrailingString( - appendedPath, + appendedOriginalPath, "/index.html" ) + search ), diff --git a/test/unit/middleware/files.spec.ts b/test/unit/middleware/files.spec.ts index c201b99f..2bc36ce5 100644 --- a/test/unit/middleware/files.spec.ts +++ b/test/unit/middleware/files.spec.ts @@ -155,289 +155,350 @@ describe("i18n", () => { }); }); -describe("static server with trailing slash customization", () => { - const provider = fsProvider({ - public: ".tmp", - }); - let app: connect.Server; - - beforeEach(async () => { - await fs.mkdir(".tmp", { recursive: true }); - await fs.writeFile(".tmp/foo.html", "foo.html content", "utf8"); - await fs.mkdir(".tmp/foo", { recursive: true }); - await fs.writeFile(".tmp/foo/index.html", "foo/index.html content", "utf8"); - await fs.writeFile(".tmp/foo/bar.html", "foo/bar.html content", "utf8"); - - app = connect().use( - (req, res: ServerResponse & { superstatic?: Responder }, next) => { - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - res.superstatic = new Responder(req, res, { provider }); - next(); +const basePathConfigs = [ + ["", "static server with trailing slash customization"], + [ + "/base/path", + "static server under base path, with trailing slash customization", + ], +]; + +basePathConfigs.forEach(([basePath, testTitle]) => { + describe(testTitle, () => { + const provider = fsProvider({ + public: ".tmp", + }); + let app: connect.Server; + function appUse( + middleware: connect.HandleFunction + ): ReturnType { + if (basePath === "") { + return app.use(middleware); + } else { + return app.use(basePath, middleware); } - ); - }); + } + + beforeEach(async () => { + await fs.mkdir(".tmp", { recursive: true }); + await fs.writeFile(".tmp/foo.html", "foo.html content", "utf8"); + await fs.mkdir(".tmp/foo", { recursive: true }); + await fs.writeFile( + ".tmp/foo/index.html", + "foo/index.html content", + "utf8" + ); + await fs.writeFile(".tmp/foo/bar.html", "foo/bar.html content", "utf8"); + + app = connect(); + appUse( + ( + req: connect.IncomingMessage, + res: ServerResponse & { superstatic?: Responder }, + next: connect.NextFunction + ) => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + res.superstatic = new Responder(req, res, { provider }); + next(); + } + ); + }); - afterEach(async () => { - await fs.rm(".tmp", { recursive: true, force: true }); - }); + afterEach(async () => { + await fs.rm(".tmp", { recursive: true, force: true }); + }); - it("serves html file", async () => { - app.use(files({}, { provider: provider })); + it("serves html file", async () => { + appUse(files({}, { provider: provider })); - await request(app) - .get("/foo.html") - .expect(200) - .expect("foo.html content") - .expect("content-type", "text/html; charset=utf-8"); - }); + await request(app) + .get(basePath + "/foo.html") + .expect(200) + .expect("foo.html content") + .expect("content-type", "text/html; charset=utf-8"); + }); - it("serves html file with unicode name", async () => { - await fs.writeFile(".tmp/äää.html", "test", "utf8"); + it("serves html file with unicode name", async () => { + await fs.writeFile(".tmp/äää.html", "test", "utf8"); - app.use(files({}, { provider: provider })); + appUse(files({}, { provider: provider })); - await request(app) - .get("/äää.html") - .expect(200) - .expect("test") - .expect("content-type", "text/html; charset=utf-8"); - }); + await request(app) + .get(basePath + "/äää.html") + .expect(200) + .expect("test") + .expect("content-type", "text/html; charset=utf-8"); + }); - it("serves css file", async () => { - await fs.writeFile(".tmp/style.css", "body {}", "utf8"); + it("serves css file", async () => { + await fs.writeFile(".tmp/style.css", "body {}", "utf8"); - app.use(files({}, { provider: provider })); + appUse(files({}, { provider: provider })); - await request(app) - .get("/style.css") - .expect(200) - .expect("body {}") - .expect("content-type", "text/css; charset=utf-8"); - }); + await request(app) + .get(basePath + "/style.css") + .expect(200) + .expect("body {}") + .expect("content-type", "text/css; charset=utf-8"); + }); - it("serves a directory index file", async () => { - await fs.writeFile(".tmp/index.html", "test", "utf8"); + it("serves a directory index file", async () => { + await fs.writeFile(".tmp/index.html", "test", "utf8"); - app.use(files({}, { provider: provider })); + appUse(files({}, { provider: provider })); - await request(app) - .get("/") - .expect(200) - .expect("test") - .expect("content-type", "text/html; charset=utf-8"); - }); + await request(app) + .get(basePath + "/") + .expect(200) + .expect("test") + .expect("content-type", "text/html; charset=utf-8"); + }); - it("serves a file with query parameters", async () => { - await fs.writeFile(".tmp/superstatic.html", "test", "utf8"); + it("serves a file with query parameters", async () => { + await fs.writeFile(".tmp/superstatic.html", "test", "utf8"); - app.use(files({}, { provider: provider })); + appUse(files({}, { provider: provider })); - await request(app) - .get("/superstatic.html?key=value") - .expect(200) - .expect("test"); - }); + await request(app) + .get(basePath + "/superstatic.html?key=value") + .expect(200) + .expect("test"); + }); - it("does not redirect the root url because of the trailing slash", async () => { - await fs.writeFile(".tmp/index.html", "an actual index", "utf8"); + it("does not redirect the root url because of the trailing slash", async () => { + await fs.writeFile(".tmp/index.html", "an actual index", "utf8"); - app.use(files({}, { provider: provider })); + appUse(files({}, { provider: provider })); - await request(app).get("/").expect(200).expect("an actual index"); - }); - - it("does not redirect for directory index files", async () => { - app.use(files({}, { provider: provider })); + await request(app) + .get(basePath + "/") + .expect(200) + .expect("an actual index"); + }); - await request(app) - .get("/foo/") - .expect(200) - .expect("foo/index.html content"); - }); + it("does not redirect for directory index files", async () => { + appUse(files({}, { provider: provider })); - it("function() directory index to have a trailing slash", async () => { - app.use(files({}, { provider: provider })); + await request(app) + .get(basePath + "/foo/") + .expect(200) + .expect("foo/index.html content"); + }); - await request(app) - .get("/foo") - .expect((res) => { - expect(res.headers.location).to.equal("/foo/"); - }) - .expect(301); - }); + it("function() directory index to have a trailing slash", async () => { + appUse(files({}, { provider: provider })); - it("preserves query parameters and slash on subdirectory directory index redirect", async () => { - app.use(files({}, { provider: provider })); + await request(app) + .get(basePath + "/foo") + .expect((res) => { + expect(res.headers.location).to.equal(basePath + "/foo/"); + }) + .expect(301); + }); - await request(app) - .get("/foo?query=params") - .expect((req) => { - expect(req.headers.location).to.equal("/foo/?query=params"); - }) - .expect(301); - }); + it("preserves query parameters and slash on subdirectory directory index redirect", async () => { + appUse(files({}, { provider: provider })); + + await request(app) + .get(basePath + "/foo?query=params") + .expect((req) => { + expect(req.headers.location).to.equal( + basePath + "/foo/?query=params" + ); + }) + .expect(301); + }); - describe("force trailing slash", () => { - it("adds slash to url with no extension", async () => { - app.use(files({ trailingSlash: true }, { provider: provider })); + describe("force trailing slash", () => { + it("adds slash to url with no extension", async () => { + appUse(files({ trailingSlash: true }, { provider: provider })); - await request(app).get("/foo").expect(301).expect("Location", "/foo/"); + await request(app) + .get(basePath + "/foo") + .expect(301) + .expect("Location", basePath + "/foo/"); + }); }); - }); - describe("force remove trailing slash", () => { - it("removes trailing slash on urls with no file extension", async () => { - app.use(files({ trailingSlash: false }, { provider: provider })); + describe("force remove trailing slash", () => { + it("removes trailing slash on urls with no file extension", async () => { + appUse(files({ trailingSlash: false }, { provider: provider })); - await request(app).get("/foo/").expect(301).expect("Location", "/foo"); - }); + await request(app) + .get(basePath + "/foo/") + .expect(301) + .expect("Location", basePath + "/foo"); + }); - it("returns a 404 if a trailing slash was added to a valid path", async () => { - app.use(files({ trailingSlash: false }, { provider: provider })); + it("returns a 404 if a trailing slash was added to a valid path", async () => { + appUse(files({ trailingSlash: false }, { provider: provider })); - await request(app).get("/foo.html/").expect(404); - }); + await request(app) + .get(basePath + "/foo.html/") + .expect(404); + }); - it("removes trailing slash on directory index urls", async () => { - app.use(files({ trailingSlash: false }, { provider: provider })); + it("removes trailing slash on directory index urls", async () => { + appUse(files({ trailingSlash: false }, { provider: provider })); - await request(app).get("/foo/").expect(301).expect("Location", "/foo"); - }); + await request(app) + .get(basePath + "/foo/") + .expect(301) + .expect("Location", basePath + "/foo"); + }); - it("normalizes multiple leading slashes on a redirect", async () => { - app.use(files({ trailingSlash: false }, { provider: provider })); + it("normalizes multiple leading slashes on a redirect", async () => { + appUse(files({ trailingSlash: false }, { provider: provider })); - await request(app).get("/foo////").expect(301).expect("Location", "/foo"); + await request(app) + .get(basePath + "/foo////") + .expect(301) + .expect("Location", basePath + "/foo"); + }); }); - }); - [ - { - trailingSlashBehavior: undefined, - cleanUrls: false, - tests: [ - { path: "/foo", wantRedirect: "/foo/" }, - { path: "/foo.html", wantContent: "foo.html content" }, - { path: "/foo.html/", wantNotFound: true }, - { path: "/foo/", wantContent: "foo/index.html content" }, - { path: "/foo/bar", wantNotFound: true }, - { path: "/foo/bar.html", wantContent: "foo/bar.html content" }, - { path: "/foo/bar.html/", wantNotFound: true }, - { path: "/foo/bar/", wantNotFound: true }, - { path: "/foo/index", wantNotFound: true }, - { path: "/foo/index.html", wantContent: "foo/index.html content" }, - { path: "/foo/index.html/", wantNotFound: true }, - ], - }, - { - trailingSlashBehavior: false, - cleanUrls: false, - tests: [ - { path: "/foo", wantContent: "foo/index.html content" }, - { path: "/foo.html", wantContent: "foo.html content" }, - { path: "/foo.html/", wantNotFound: true }, - { path: "/foo/", wantRedirect: "/foo" }, - { path: "/foo/bar", wantNotFound: true }, - { path: "/foo/bar.html", wantContent: "foo/bar.html content" }, - { path: "/foo/bar.html/", wantNotFound: true }, - { path: "/foo/bar/", wantNotFound: true }, - { path: "/foo/index", wantNotFound: true }, - { path: "/foo/index.html", wantContent: "foo/index.html content" }, - { path: "/foo/index.html/", wantNotFound: true }, - ], - }, - { - trailingSlashBehavior: true, - cleanUrls: false, - tests: [ - { path: "/foo", wantRedirect: "/foo/" }, - { path: "/foo.html", wantContent: "foo.html content" }, - { path: "/foo.html/", wantNotFound: true }, - { path: "/foo/", wantContent: "foo/index.html content" }, - { path: "/foo/bar", wantNotFound: true }, - { path: "/foo/bar.html", wantContent: "foo/bar.html content" }, - { path: "/foo/bar.html/", wantNotFound: true }, - { path: "/foo/bar/", wantNotFound: true }, - { path: "/foo/index", wantNotFound: true }, - { path: "/foo/index.html", wantContent: "foo/index.html content" }, - { path: "/foo/index.html/", wantNotFound: true }, - ], - }, - { - trailingSlashBehavior: undefined, - cleanUrls: true, - tests: [ - { path: "/foo", wantContent: "foo/index.html content" }, - { path: "/foo.html", wantRedirect: "/foo" }, - { path: "/foo.html/", wantNotFound: true }, - { path: "/foo/", wantContent: "foo/index.html content" }, - { path: "/foo/bar", wantContent: "foo/bar.html content" }, - { path: "/foo/bar.html", wantRedirect: "/foo/bar" }, - { path: "/foo/bar.html/", wantNotFound: true }, - { path: "/foo/bar/", wantNotFound: true }, - { path: "/foo/index", wantRedirect: "/foo" }, - { path: "/foo/index.html", wantRedirect: "/foo" }, - { path: "/foo/index.html/", wantNotFound: true }, - ], - }, - { - trailingSlashBehavior: false, - cleanUrls: true, - tests: [ - { path: "/foo", wantContent: "foo/index.html content" }, - { path: "/foo.html", wantRedirect: "/foo" }, - { path: "/foo.html/", wantNotFound: true }, - { path: "/foo/", wantRedirect: "/foo" }, - { path: "/foo/bar", wantContent: "foo/bar.html content" }, - { path: "/foo/bar.html", wantRedirect: "/foo/bar" }, - { path: "/foo/bar.html/", wantNotFound: true }, - { path: "/foo/bar/", wantRedirect: "/foo/bar" }, - { path: "/foo/index", wantRedirect: "/foo" }, - { path: "/foo/index.html", wantRedirect: "/foo" }, - { path: "/foo/index.html/", wantNotFound: true }, - ], - }, - { - trailingSlashBehavior: true, - cleanUrls: true, - tests: [ - { path: "/foo", wantRedirect: "/foo/" }, - { path: "/foo.html", wantRedirect: "/foo/" }, - { path: "/foo.html/", wantNotFound: true }, - { path: "/foo/", wantContent: "foo/index.html content" }, - { path: "/foo/bar", wantRedirect: "/foo/bar/" }, - { path: "/foo/bar.html", wantRedirect: "/foo/bar/" }, - { path: "/foo/bar.html/", wantNotFound: true }, - { path: "/foo/bar/", wantContent: "foo/bar.html content" }, - { path: "/foo/index", wantRedirect: "/foo/" }, - { path: "/foo/index.html", wantRedirect: "/foo/" }, - { path: "/foo/index.html/", wantNotFound: true }, - ], - }, - ].forEach((t) => { - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - const desc = `trailing slash ${t.trailingSlashBehavior} cleanUrls ${t.cleanUrls}`; - t.tests.forEach((tt) => { - const ttDesc = `${desc} ${JSON.stringify(tt)}`; - it("should behave correctly: " + ttDesc, async () => { - app.use( - files( - { trailingSlash: t.trailingSlashBehavior, cleanUrls: t.cleanUrls }, - { provider: provider } + [ + { + trailingSlashBehavior: undefined, + cleanUrls: false, + tests: [ + { path: "/foo", wantRedirect: "/foo/" }, + { path: "/foo.html", wantContent: "foo.html content" }, + { path: "/foo.html/", wantNotFound: true }, + { path: "/foo/", wantContent: "foo/index.html content" }, + { path: "/foo/bar", wantNotFound: true }, + { path: "/foo/bar.html", wantContent: "foo/bar.html content" }, + { path: "/foo/bar.html/", wantNotFound: true }, + { path: "/foo/bar/", wantNotFound: true }, + { path: "/foo/index", wantNotFound: true }, + { path: "/foo/index.html", wantContent: "foo/index.html content" }, + { path: "/foo/index.html/", wantNotFound: true }, + ], + }, + { + trailingSlashBehavior: false, + cleanUrls: false, + tests: [ + { path: "/foo", wantContent: "foo/index.html content" }, + { path: "/foo.html", wantContent: "foo.html content" }, + { path: "/foo.html/", wantNotFound: true }, + { path: "/foo/", wantRedirect: "/foo" }, + { path: "/foo/bar", wantNotFound: true }, + { path: "/foo/bar.html", wantContent: "foo/bar.html content" }, + { path: "/foo/bar.html/", wantNotFound: true }, + { path: "/foo/bar/", wantNotFound: true }, + { path: "/foo/index", wantNotFound: true }, + { path: "/foo/index.html", wantContent: "foo/index.html content" }, + { path: "/foo/index.html/", wantNotFound: true }, + ], + }, + { + trailingSlashBehavior: true, + cleanUrls: false, + tests: [ + { path: "/foo", wantRedirect: "/foo/" }, + { path: "/foo.html", wantContent: "foo.html content" }, + { path: "/foo.html/", wantNotFound: true }, + { path: "/foo/", wantContent: "foo/index.html content" }, + { path: "/foo/bar", wantNotFound: true }, + { path: "/foo/bar.html", wantContent: "foo/bar.html content" }, + { path: "/foo/bar.html/", wantNotFound: true }, + { path: "/foo/bar/", wantNotFound: true }, + { path: "/foo/index", wantNotFound: true }, + { path: "/foo/index.html", wantContent: "foo/index.html content" }, + { path: "/foo/index.html/", wantNotFound: true }, + ], + }, + { + trailingSlashBehavior: undefined, + cleanUrls: true, + tests: [ + { path: "/foo", wantContent: "foo/index.html content" }, + { path: "/foo.html", wantRedirect: "/foo" }, + { path: "/foo.html/", wantNotFound: true }, + { path: "/foo/", wantContent: "foo/index.html content" }, + { path: "/foo/bar", wantContent: "foo/bar.html content" }, + { path: "/foo/bar.html", wantRedirect: "/foo/bar" }, + { path: "/foo/bar.html/", wantNotFound: true }, + { path: "/foo/bar/", wantNotFound: true }, + { path: "/foo/index", wantRedirect: "/foo" }, + { path: "/foo/index.html", wantRedirect: "/foo" }, + { path: "/foo/index.html/", wantNotFound: true }, + ], + }, + { + trailingSlashBehavior: false, + cleanUrls: true, + tests: [ + { path: "/foo", wantContent: "foo/index.html content" }, + { path: "/foo.html", wantRedirect: "/foo" }, + { path: "/foo.html/", wantNotFound: true }, + { path: "/foo/", wantRedirect: "/foo" }, + { path: "/foo/bar", wantContent: "foo/bar.html content" }, + { path: "/foo/bar.html", wantRedirect: "/foo/bar" }, + { path: "/foo/bar.html/", wantNotFound: true }, + { path: "/foo/bar/", wantRedirect: "/foo/bar" }, + { path: "/foo/index", wantRedirect: "/foo" }, + { path: "/foo/index.html", wantRedirect: "/foo" }, + { path: "/foo/index.html/", wantNotFound: true }, + ], + }, + { + trailingSlashBehavior: true, + cleanUrls: true, + tests: [ + { path: "/foo", wantRedirect: "/foo/" }, + { path: "/foo.html", wantRedirect: "/foo/" }, + { path: "/foo.html/", wantNotFound: true }, + { path: "/foo/", wantContent: "foo/index.html content" }, + { path: "/foo/bar", wantRedirect: "/foo/bar/" }, + { path: "/foo/bar.html", wantRedirect: "/foo/bar/" }, + { path: "/foo/bar.html/", wantNotFound: true }, + { path: "/foo/bar/", wantContent: "foo/bar.html content" }, + { path: "/foo/index", wantRedirect: "/foo/" }, + { path: "/foo/index.html", wantRedirect: "/foo/" }, + { path: "/foo/index.html/", wantNotFound: true }, + ], + }, + ].forEach((t) => { + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + const desc = `trailing slash ${t.trailingSlashBehavior} cleanUrls ${t.cleanUrls}`; + t.tests + .map((props) => + Object.assign( + {}, + props, + { path: basePath + props.path }, + typeof props.wantRedirect === "string" + ? { wantRedirect: basePath + props.wantRedirect } + : {} ) - ); - - const r = request(app).get(tt.path); - if (tt.wantRedirect) { - await r.expect(301).expect("Location", tt.wantRedirect); - } else if (tt.wantNotFound) { - await r.expect(404); - } else if (tt.wantContent) { - await r.expect(200).expect(tt.wantContent); - } else { - throw new Error("Test set up incorrectly"); - } - }); + ) + .forEach((tt) => { + const ttDesc = `${desc} ${JSON.stringify(tt)}`; + it("should behave correctly: " + ttDesc, async () => { + appUse( + files( + { + trailingSlash: t.trailingSlashBehavior, + cleanUrls: t.cleanUrls, + }, + { provider: provider } + ) + ); + + const r = request(app).get(tt.path); + if (tt.wantRedirect) { + await r.expect(301).expect("Location", tt.wantRedirect); + } else if (tt.wantNotFound) { + await r.expect(404); + } else if (tt.wantContent) { + await r.expect(200).expect(tt.wantContent); + } else { + throw new Error("Test set up incorrectly"); + } + }); + }); }); }); });