|
| 1 | +import { matchPath } from "@remix-run/router"; |
1 | 2 | import { existsSync, readdirSync, statSync } from "node:fs"; |
2 | 3 | import { join } from "node:path"; |
3 | 4 | import { describe, expect, it } from "vitest"; |
4 | | -import { deeplinkSuffix, ENV_PAGE_TARGETS, resolveDeeplinkPage } from "./deeplinkPages"; |
| 5 | +import { |
| 6 | + DEEPLINK_PATH_PREFIX, |
| 7 | + deeplinkSuffix, |
| 8 | + ENV_PAGE_TARGETS, |
| 9 | + resolveDeeplinkPage, |
| 10 | +} from "./deeplinkPages"; |
5 | 11 |
|
6 | 12 | const ROUTES_DIR = join(__dirname, "../routes"); |
7 | 13 |
|
@@ -181,6 +187,35 @@ describe("resolveDeeplinkPage", () => { |
181 | 187 | expect(resolveDeeplinkPage("metrics")).toBeUndefined(); |
182 | 188 | }); |
183 | 189 |
|
| 190 | + it("matches the page name whatever its case, and resolves it to the map's spelling", () => { |
| 191 | + // `/env/{env}/APIKeys` matches its route, so the short link has to agree rather than falling |
| 192 | + // through to the environment root. |
| 193 | + expect(resolveDeeplinkPage("APIKeys")).toBe("apikeys"); |
| 194 | + expect(resolveDeeplinkPage("Waitpoints")).toBe("waitpoints/tokens"); |
| 195 | + expect(resolveDeeplinkPage("TASKS")).toBe(""); |
| 196 | + expect(resolveDeeplinkPage("Bulk-Actions")).toBe("bulk-actions"); |
| 197 | + // Case doesn't turn a non-page into a page. |
| 198 | + expect(resolveDeeplinkPage("Nonsense")).toBeUndefined(); |
| 199 | + expect(resolveDeeplinkPage("Metrics")).toBeUndefined(); |
| 200 | + }); |
| 201 | + |
| 202 | + it("leaves the case of everything after the name alone", () => { |
| 203 | + // Only the name is folded. Ids are case-sensitive, so lowercasing one would break the link far |
| 204 | + // more thoroughly than the miss the folding fixes. |
| 205 | + expect(resolveDeeplinkPage("runs/run_ABC123")).toBe("runs/run_ABC123"); |
| 206 | + expect(resolveDeeplinkPage("Runs/run_ABC123")).toBe("runs/run_ABC123"); |
| 207 | + expect(resolveDeeplinkPage("TASKS/standard/My-Task")).toBe("tasks/standard/My-Task"); |
| 208 | + // Grafted onto the prefix and already written out under it, both with the id untouched. |
| 209 | + expect(resolveDeeplinkPage("Waitpoints/waitpoint_ABC")).toBe("waitpoints/tokens/waitpoint_ABC"); |
| 210 | + expect(resolveDeeplinkPage("Waitpoints/tokens/waitpoint_ABC")).toBe( |
| 211 | + "waitpoints/tokens/waitpoint_ABC" |
| 212 | + ); |
| 213 | + // An escaped slash inside a capitalised id survives as one segment, as it does in lower case. |
| 214 | + expect(resolveDeeplinkPage("Tasks/standard/Group%2FMy-Task")).toBe( |
| 215 | + "tasks/standard/Group%2FMy-Task" |
| 216 | + ); |
| 217 | + }); |
| 218 | + |
184 | 219 | it("drops traversal segments, in plain and escaped spellings", () => { |
185 | 220 | expect(resolveDeeplinkPage("runs/../../../etc/passwd")).toBe("runs/etc/passwd"); |
186 | 221 | expect(resolveDeeplinkPage("../runs")).toBe("runs"); |
@@ -216,6 +251,26 @@ describe("deeplinkSuffix", () => { |
216 | 251 | ); |
217 | 252 | }); |
218 | 253 |
|
| 254 | + it("strips the prefix whatever its case, and only the prefix", () => { |
| 255 | + expect(deeplinkSuffix("/Deeplink/apikeys")).toBe("apikeys"); |
| 256 | + expect(deeplinkSuffix("/DEEPLINK/runs/run_ABC123")).toBe("runs/run_ABC123"); |
| 257 | + // The remainder comes back as it was written, capitals and all. |
| 258 | + expect(deeplinkSuffix("/DeepLink/tasks/standard/My-Task")).toBe("tasks/standard/My-Task"); |
| 259 | + expect(deeplinkSuffix("/Deeplink")).toBe(""); |
| 260 | + expect(deeplinkSuffix("/Deeplink/")).toBe(""); |
| 261 | + }); |
| 262 | + |
| 263 | + it("folds case because the route it is mounted on does", () => { |
| 264 | + // The assertion the test above rests on: React Router compiles a route path with the `i` flag |
| 265 | + // unless it opts into `caseSensitive`, so a capitalised prefix really does reach this loader |
| 266 | + // instead of 404ing before it. If that ever changed, the folding would be dead weight. |
| 267 | + const route = `${DEEPLINK_PATH_PREFIX}/*`; |
| 268 | + expect(matchPath(route, "/deeplink/apikeys")?.params["*"]).toBe("apikeys"); |
| 269 | + expect(matchPath(route, "/Deeplink/apikeys")?.params["*"]).toBe("apikeys"); |
| 270 | + // And the splat keeps the case it was given, which is why only the first segment is folded. |
| 271 | + expect(matchPath(route, "/DEEPLINK/APIKeys")?.params["*"]).toBe("APIKeys"); |
| 272 | + }); |
| 273 | + |
219 | 274 | it("treats a bare prefix, a trailing slash and anything outside it as no suffix", () => { |
220 | 275 | expect(deeplinkSuffix("/deeplink")).toBe(""); |
221 | 276 | expect(deeplinkSuffix("/deeplink/")).toBe(""); |
|
0 commit comments