test(docs-infra): update firebase-test-utils to support regex-based redirects (#41842)
This commit updates the utilities in `firebase-test-utils/` to also support testing Firebase redirects that are configured using regular expressions (via the `regex` property). See the [Firebase docs][1] for more details. [1]: https://firebase.google.com/docs/hosting/full-config#redirects PR Close #41842
This commit is contained in:
committed by
Jessica Janiuk
parent
ae1ce5f9c7
commit
d1f5a9e44b
@@ -3,18 +3,28 @@ import { FirebaseRedirect } from './FirebaseRedirect';
|
||||
describe('FirebaseRedirect', () => {
|
||||
describe('replace', () => {
|
||||
it('should return undefined if the redirect does not match the url', () => {
|
||||
const redirect = new FirebaseRedirect({source: '/a/b/c', destination: '/x/y/z'});
|
||||
expect(redirect.replace('/1/2/3')).toBe(undefined);
|
||||
const globRedirect = new FirebaseRedirect({source: '/a/b/c', destination: '/x/y/z'});
|
||||
expect(globRedirect.replace('/1/2/3')).toBe(undefined);
|
||||
|
||||
const regexRedirect = new FirebaseRedirect({regex: '^/a/b/c$', destination: '/x/y/z'});
|
||||
expect(regexRedirect.replace('/1/2/3')).toBe(undefined);
|
||||
});
|
||||
|
||||
it('should return the destination if there is a match', () => {
|
||||
const redirect = new FirebaseRedirect({source: '/a/b/c', destination: '/x/y/z'});
|
||||
expect(redirect.replace('/a/b/c')).toBe('/x/y/z');
|
||||
const globRedirect = new FirebaseRedirect({source: '/a/b/c', destination: '/x/y/z'});
|
||||
expect(globRedirect.replace('/a/b/c')).toBe('/x/y/z');
|
||||
|
||||
const regexRedirect = new FirebaseRedirect({regex: '^/a/b/c$', destination: '/x/y/z'});
|
||||
expect(regexRedirect.replace('/a/b/c')).toBe('/x/y/z');
|
||||
});
|
||||
|
||||
it('should inject name params into the destination', () => {
|
||||
const redirect = new FirebaseRedirect({source: '/api/:package/:api-*', destination: '<:package><:api>'});
|
||||
expect(redirect.replace('/api/common/NgClass-directive')).toEqual('<common><NgClass>');
|
||||
const globRedirect = new FirebaseRedirect({source: '/api/:package/:api-*', destination: '<:package><:api>'});
|
||||
expect(globRedirect.replace('/api/common/NgClass-directive')).toBe('<common><NgClass>');
|
||||
|
||||
const regexRedirect = new FirebaseRedirect(
|
||||
{regex: '^/api/(?P<package>[^/]+)/(?P<api>[^/]+)-.*$', destination: '<:package><:api>'});
|
||||
expect(regexRedirect.replace('/api/common/NgClass-directive')).toBe('<common><NgClass>');
|
||||
});
|
||||
|
||||
it('should inject rest params into the destination', () => {
|
||||
|
||||
@@ -6,8 +6,10 @@ export class FirebaseRedirect {
|
||||
source: FirebaseRedirectSource;
|
||||
destination: string;
|
||||
|
||||
constructor({source, destination}: FirebaseRedirectConfig) {
|
||||
this.source = FirebaseRedirectSource.fromGlobPattern(source);
|
||||
constructor({source, regex, destination}: FirebaseRedirectConfig) {
|
||||
this.source = (typeof source === 'string') ?
|
||||
FirebaseRedirectSource.fromGlobPattern(source) :
|
||||
FirebaseRedirectSource.fromRegexPattern(regex!);
|
||||
this.destination = destination;
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -53,6 +53,29 @@ export class FirebaseRedirectSource {
|
||||
}
|
||||
}
|
||||
|
||||
static fromRegexPattern(regex: string): FirebaseRedirectSource {
|
||||
try {
|
||||
// NOTE:
|
||||
// Firebase redirect regexes use the [RE2 library](https://github.com/google/re2/wiki/Syntax),
|
||||
// which requires named capture groups to begin with `?P`. See
|
||||
// https://firebase.google.com/docs/hosting/full-config#capture-url-segments-for-redirects.
|
||||
|
||||
if (/\(\?<[^>]+>/.test(regex)) {
|
||||
// Throw if the regex contains a non-RE2 named capture group.
|
||||
throw new Error(
|
||||
'The regular expression pattern contains a named capture group of the format ' +
|
||||
'`(?<name>...)`, which is not compatible with the RE2 library. Use `(?P<name>...)` ' +
|
||||
'instead.');
|
||||
}
|
||||
|
||||
// Replace `(?P<...>` with just `(?<...>` to convert to `XRegExp`-compatible syntax for named
|
||||
// capture groups.
|
||||
return new FirebaseRedirectSource(regex.replace(/(\(\?)P(<[^>]+>)/g, '$1$2'));
|
||||
} catch (err) {
|
||||
throw new Error(`Error in FirebaseRedirectSource: "${regex}" - ${err.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
test(url: string): boolean {
|
||||
return XRegExp.test(url, this.regex);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ describe('FirebaseRedirector', () => {
|
||||
it('should replace with the first matching redirect', () => {
|
||||
const redirector = new FirebaseRedirector([
|
||||
{ source: '/a/b/c', destination: '/X/Y/Z' },
|
||||
{ source: '/a/:foo/c', destination: '/X/:foo/Z' },
|
||||
{ regex: '^/a/(?P<foo>[^/]+)/c$', destination: '/X/:foo/Z' },
|
||||
{ source: '/**/:foo/c', destination: '/A/:foo/zzz' },
|
||||
]);
|
||||
expect(redirector.redirect('/a/b/c')).toEqual('/X/Y/Z');
|
||||
@@ -15,9 +15,9 @@ describe('FirebaseRedirector', () => {
|
||||
|
||||
it('should return the original url if no redirect matches', () => {
|
||||
const redirector = new FirebaseRedirector([
|
||||
{ source: 'x', destination: 'X' },
|
||||
{ regex: '^x$', destination: 'X' },
|
||||
{ source: 'y', destination: 'Y' },
|
||||
{ source: 'z', destination: 'Z' },
|
||||
{ regex: '^z$', destination: 'Z' },
|
||||
]);
|
||||
expect(redirector.redirect('a')).toEqual('a');
|
||||
});
|
||||
@@ -25,7 +25,7 @@ describe('FirebaseRedirector', () => {
|
||||
it('should recursively redirect', () => {
|
||||
const redirector = new FirebaseRedirector([
|
||||
{ source: 'a', destination: 'b' },
|
||||
{ source: 'b', destination: 'c' },
|
||||
{ regex: '^b$', destination: 'c' },
|
||||
{ source: 'c', destination: 'd' },
|
||||
]);
|
||||
expect(redirector.redirect('a')).toEqual('d');
|
||||
@@ -33,9 +33,9 @@ describe('FirebaseRedirector', () => {
|
||||
|
||||
it('should throw if stuck in an infinite loop', () => {
|
||||
const redirector = new FirebaseRedirector([
|
||||
{ source: 'a', destination: 'b' },
|
||||
{ regex: '^a$', destination: 'b' },
|
||||
{ source: 'b', destination: 'c' },
|
||||
{ source: 'c', destination: 'a' },
|
||||
{ regex: '^c$', destination: 'a' },
|
||||
]);
|
||||
expect(() => redirector.redirect('a')).toThrowError('infinite redirect loop');
|
||||
});
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { FirebaseRedirect } from './FirebaseRedirect';
|
||||
|
||||
export interface FirebaseRedirectConfig {
|
||||
source: string;
|
||||
destination: string;
|
||||
}
|
||||
export type FirebaseRedirectConfig =
|
||||
{ source: string, regex?: undefined, destination: string } |
|
||||
{ source?: undefined, regex: string, destination: string };
|
||||
|
||||
export class FirebaseRedirector {
|
||||
private redirects: FirebaseRedirect[];
|
||||
|
||||
Reference in New Issue
Block a user