mirror of
https://github.com/fosrl/pangolin.git
synced 2026-01-28 22:00:51 +00:00
284 lines
8.3 KiB
TypeScript
284 lines
8.3 KiB
TypeScript
import { assertEquals } from "@test/assert";
|
|
|
|
function isPathAllowed(pattern: string, path: string): boolean {
|
|
// Normalize and split paths into segments
|
|
const normalize = (p: string) => p.split("/").filter(Boolean);
|
|
const patternParts = normalize(pattern);
|
|
const pathParts = normalize(path);
|
|
|
|
// Recursive function to try different wildcard matches
|
|
function matchSegments(patternIndex: number, pathIndex: number): boolean {
|
|
const indent = " ".repeat(pathIndex); // Indent based on recursion depth
|
|
const currentPatternPart = patternParts[patternIndex];
|
|
const currentPathPart = pathParts[pathIndex];
|
|
|
|
// If we've consumed all pattern parts, we should have consumed all path parts
|
|
if (patternIndex >= patternParts.length) {
|
|
const result = pathIndex >= pathParts.length;
|
|
return result;
|
|
}
|
|
|
|
// If we've consumed all path parts but still have pattern parts
|
|
if (pathIndex >= pathParts.length) {
|
|
// The only way this can match is if all remaining pattern parts are wildcards
|
|
const remainingPattern = patternParts.slice(patternIndex);
|
|
const result = remainingPattern.every((p) => p === "*");
|
|
return result;
|
|
}
|
|
|
|
// For full segment wildcards, try consuming different numbers of path segments
|
|
if (currentPatternPart === "*") {
|
|
// Try consuming 0 segments (skip the wildcard)
|
|
if (matchSegments(patternIndex + 1, pathIndex)) {
|
|
return true;
|
|
}
|
|
|
|
// Try consuming current segment and recursively try rest
|
|
if (matchSegments(patternIndex, pathIndex + 1)) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// Check for in-segment wildcard (e.g., "prefix*" or "prefix*suffix")
|
|
if (currentPatternPart.includes("*")) {
|
|
// Convert the pattern segment to a regex pattern
|
|
const regexPattern = currentPatternPart
|
|
.replace(/\*/g, ".*") // Replace * with .* for regex wildcard
|
|
.replace(/\?/g, "."); // Replace ? with . for single character wildcard if needed
|
|
|
|
const regex = new RegExp(`^${regexPattern}$`);
|
|
|
|
if (regex.test(currentPathPart)) {
|
|
return matchSegments(patternIndex + 1, pathIndex + 1);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// For regular segments, they must match exactly
|
|
if (currentPatternPart !== currentPathPart) {
|
|
return false;
|
|
}
|
|
|
|
// Move to next segments in both pattern and path
|
|
return matchSegments(patternIndex + 1, pathIndex + 1);
|
|
}
|
|
|
|
const result = matchSegments(0, 0);
|
|
return result;
|
|
}
|
|
|
|
function runTests() {
|
|
console.log("Running path matching tests...");
|
|
|
|
// Test exact matching
|
|
assertEquals(
|
|
isPathAllowed("foo", "foo"),
|
|
true,
|
|
"Exact match should be allowed"
|
|
);
|
|
assertEquals(
|
|
isPathAllowed("foo", "bar"),
|
|
false,
|
|
"Different segments should not match"
|
|
);
|
|
assertEquals(
|
|
isPathAllowed("foo/bar", "foo/bar"),
|
|
true,
|
|
"Exact multi-segment match should be allowed"
|
|
);
|
|
assertEquals(
|
|
isPathAllowed("foo/bar", "foo/baz"),
|
|
false,
|
|
"Partial multi-segment match should not be allowed"
|
|
);
|
|
|
|
// Test with leading and trailing slashes
|
|
assertEquals(
|
|
isPathAllowed("/foo", "foo"),
|
|
true,
|
|
"Pattern with leading slash should match"
|
|
);
|
|
assertEquals(
|
|
isPathAllowed("foo/", "foo"),
|
|
true,
|
|
"Pattern with trailing slash should match"
|
|
);
|
|
assertEquals(
|
|
isPathAllowed("/foo/", "foo"),
|
|
true,
|
|
"Pattern with both leading and trailing slashes should match"
|
|
);
|
|
assertEquals(
|
|
isPathAllowed("foo", "/foo/"),
|
|
true,
|
|
"Path with leading and trailing slashes should match"
|
|
);
|
|
|
|
// Test simple wildcard matching
|
|
assertEquals(
|
|
isPathAllowed("*", "foo"),
|
|
true,
|
|
"Single wildcard should match any single segment"
|
|
);
|
|
assertEquals(
|
|
isPathAllowed("*", "foo/bar"),
|
|
true,
|
|
"Single wildcard should match multiple segments"
|
|
);
|
|
assertEquals(
|
|
isPathAllowed("*/bar", "foo/bar"),
|
|
true,
|
|
"Wildcard prefix should match"
|
|
);
|
|
assertEquals(
|
|
isPathAllowed("foo/*", "foo/bar"),
|
|
true,
|
|
"Wildcard suffix should match"
|
|
);
|
|
assertEquals(
|
|
isPathAllowed("foo/*/baz", "foo/bar/baz"),
|
|
true,
|
|
"Wildcard in middle should match"
|
|
);
|
|
|
|
// Test multiple wildcards
|
|
assertEquals(
|
|
isPathAllowed("*/*", "foo/bar"),
|
|
true,
|
|
"Multiple wildcards should match corresponding segments"
|
|
);
|
|
assertEquals(
|
|
isPathAllowed("*/*/*", "foo/bar/baz"),
|
|
true,
|
|
"Three wildcards should match three segments"
|
|
);
|
|
assertEquals(
|
|
isPathAllowed("foo/*/*", "foo/bar/baz"),
|
|
true,
|
|
"Specific prefix with wildcards should match"
|
|
);
|
|
assertEquals(
|
|
isPathAllowed("*/*/baz", "foo/bar/baz"),
|
|
true,
|
|
"Wildcards with specific suffix should match"
|
|
);
|
|
|
|
// Test wildcard consumption behavior
|
|
assertEquals(
|
|
isPathAllowed("*", ""),
|
|
true,
|
|
"Wildcard should optionally consume segments"
|
|
);
|
|
assertEquals(
|
|
isPathAllowed("foo/*", "foo"),
|
|
true,
|
|
"Trailing wildcard should be optional"
|
|
);
|
|
assertEquals(
|
|
isPathAllowed("*/*", "foo"),
|
|
true,
|
|
"Multiple wildcards can match fewer segments"
|
|
);
|
|
assertEquals(
|
|
isPathAllowed("*/*/*", "foo/bar"),
|
|
true,
|
|
"Extra wildcards can be skipped"
|
|
);
|
|
|
|
// Test complex nested paths
|
|
assertEquals(
|
|
isPathAllowed("api/*/users", "api/v1/users"),
|
|
true,
|
|
"API versioning pattern should match"
|
|
);
|
|
assertEquals(
|
|
isPathAllowed("api/*/users/*", "api/v1/users/123"),
|
|
true,
|
|
"API resource pattern should match"
|
|
);
|
|
assertEquals(
|
|
isPathAllowed("api/*/users/*/profile", "api/v1/users/123/profile"),
|
|
true,
|
|
"Nested API pattern should match"
|
|
);
|
|
|
|
// Test for the requested padbootstrap* pattern
|
|
assertEquals(
|
|
isPathAllowed("padbootstrap*", "padbootstrap"),
|
|
true,
|
|
"padbootstrap* should match padbootstrap"
|
|
);
|
|
assertEquals(
|
|
isPathAllowed("padbootstrap*", "padbootstrapv1"),
|
|
true,
|
|
"padbootstrap* should match padbootstrapv1"
|
|
);
|
|
assertEquals(
|
|
isPathAllowed("padbootstrap*", "padbootstrap/files"),
|
|
false,
|
|
"padbootstrap* should not match padbootstrap/files"
|
|
);
|
|
assertEquals(
|
|
isPathAllowed("padbootstrap*/*", "padbootstrap/files"),
|
|
true,
|
|
"padbootstrap*/* should match padbootstrap/files"
|
|
);
|
|
assertEquals(
|
|
isPathAllowed("padbootstrap*/files", "padbootstrapv1/files"),
|
|
true,
|
|
"padbootstrap*/files should not match padbootstrapv1/files (wildcard is segment-based, not partial)"
|
|
);
|
|
|
|
// Test wildcard edge cases
|
|
assertEquals(
|
|
isPathAllowed("*/*/*/*/*/*", "a/b"),
|
|
true,
|
|
"Many wildcards can match few segments"
|
|
);
|
|
assertEquals(
|
|
isPathAllowed("a/*/b/*/c", "a/anything/b/something/c"),
|
|
true,
|
|
"Multiple wildcards in pattern should match corresponding segments"
|
|
);
|
|
|
|
// Test patterns with partial segment matches
|
|
assertEquals(
|
|
isPathAllowed("padbootstrap*", "padbootstrap-123"),
|
|
true,
|
|
"Wildcards in isPathAllowed should be segment-based, not character-based"
|
|
);
|
|
assertEquals(
|
|
isPathAllowed("test*", "testuser"),
|
|
true,
|
|
"Asterisk as part of segment name is treated as a literal, not a wildcard"
|
|
);
|
|
assertEquals(
|
|
isPathAllowed("my*app", "myapp"),
|
|
true,
|
|
"Asterisk in middle of segment name is treated as a literal, not a wildcard"
|
|
);
|
|
|
|
assertEquals(
|
|
isPathAllowed("/", "/"),
|
|
true,
|
|
"Root path should match root path"
|
|
);
|
|
assertEquals(
|
|
isPathAllowed("/", "/test"),
|
|
false,
|
|
"Root path should not match non-root path"
|
|
);
|
|
|
|
console.log("All tests passed!");
|
|
}
|
|
|
|
// Run all tests
|
|
try {
|
|
runTests();
|
|
} catch (error) {
|
|
console.error("Test failed:", error);
|
|
}
|