diff --git a/src/__tests__/staging-noindex.test.ts b/src/__tests__/staging-noindex.test.ts new file mode 100644 index 0000000..9c52eaa --- /dev/null +++ b/src/__tests__/staging-noindex.test.ts @@ -0,0 +1,55 @@ +import { describe, it, expect } from "vitest"; +import request from "supertest"; +import { app } from "../index.js"; + +describe("Staging noindex protection", () => { + it("should add X-Robots-Tag: noindex when hostname contains 'staging'", async () => { + const res = await request(app) + .get("/") + .set("Host", "staging.docfast.dev"); + + expect(res.headers["x-robots-tag"]).toBe("noindex, nofollow"); + }); + + it("should NOT add X-Robots-Tag on production hostname", async () => { + const res = await request(app) + .get("/") + .set("Host", "docfast.dev"); + + expect(res.headers["x-robots-tag"]).toBeUndefined(); + }); + + it("should add X-Robots-Tag on staging for API endpoints too", async () => { + const res = await request(app) + .get("/health") + .set("Host", "staging.docfast.dev"); + + expect(res.headers["x-robots-tag"]).toBe("noindex, nofollow"); + }); +}); + +describe("404 page", () => { + it("should serve branded HTML 404 for non-API paths", async () => { + const res = await request(app).get("/nonexistent-page"); + + expect(res.status).toBe(404); + expect(res.text).toContain("404"); + expect(res.text).toContain("Page Not Found"); + expect(res.text).toContain("DocFast"); + }); + + it("should serve JSON 404 for API paths", async () => { + const res = await request(app).get("/v1/nonexistent"); + + expect(res.status).toBe(404); + expect(res.body.error).toContain("Not Found"); + }); +}); + +describe("Dead code cleanup", () => { + it("should not have empty email verification comment in index.ts", async () => { + const fs = await import("fs"); + const content = fs.readFileSync(new URL("../index.ts", import.meta.url), "utf-8"); + expect(content).not.toContain("// Email verification endpoint\n\n"); + }); +}); diff --git a/src/index.ts b/src/index.ts index ecd5a6b..7dd595e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -56,6 +56,14 @@ app.use((_req, res, next) => { // Compression app.use(compressionMiddleware); +// Block search engine indexing on staging +app.use((req, res, next) => { + if (req.hostname.includes("staging")) { + res.setHeader("X-Robots-Tag", "noindex, nofollow"); + } + next(); +}); + // Differentiated CORS middleware app.use((req, res, next) => { const isAuthBillingRoute = req.path.startsWith('/v1/signup') || @@ -214,9 +222,6 @@ app.post("/admin/cleanup", authMiddleware, adminAuth, async (_req: any, res: any } }); -// Email verification endpoint - - // Landing page const __dirname = path.dirname(fileURLToPath(import.meta.url));