1212 lines
46 KiB
JavaScript
1212 lines
46 KiB
JavaScript
'use strict';
|
|
|
|
const request = require('../lib/request');
|
|
const fs = require('fs').promises;
|
|
const mustache = require('mustache');
|
|
const Utils = require('../lib/Utils');
|
|
const { Page } = require('../lib/Page');
|
|
const Config = require('../lib/Config');
|
|
const Definitions = require('../lib/Options/Definitions');
|
|
const UserController = require('../lib/Controllers/UserController').UserController;
|
|
const {
|
|
PagesRouter,
|
|
pages,
|
|
pageParams,
|
|
pageParamHeaderPrefix,
|
|
} = require('../lib/Routers/PagesRouter');
|
|
|
|
describe('Pages Router', () => {
|
|
describe('basic request', () => {
|
|
let config;
|
|
|
|
beforeEach(async () => {
|
|
config = {
|
|
appId: 'test',
|
|
appName: 'exampleAppname',
|
|
publicServerURL: 'http://localhost:8378/1',
|
|
pages: { enableRouter: true },
|
|
};
|
|
await reconfigureServer(config);
|
|
});
|
|
|
|
it('responds with file content on direct page request', async () => {
|
|
const urls = [
|
|
'http://localhost:8378/1/apps/email_verification_link_invalid.html',
|
|
'http://localhost:8378/1/apps/choose_password?appId=test',
|
|
'http://localhost:8378/1/apps/email_verification_success.html',
|
|
'http://localhost:8378/1/apps/password_reset_success.html',
|
|
'http://localhost:8378/1/apps/custom_json.html',
|
|
];
|
|
for (const url of urls) {
|
|
const response = await request({ url }).catch(e => e);
|
|
expect(response.status).toBe(200);
|
|
}
|
|
});
|
|
|
|
it('can load file from custom pages path', async () => {
|
|
config.pages.pagesPath = './public';
|
|
await reconfigureServer(config);
|
|
|
|
const response = await request({
|
|
url: 'http://localhost:8378/1/apps/email_verification_link_invalid.html',
|
|
}).catch(e => e);
|
|
expect(response.status).toBe(200);
|
|
});
|
|
|
|
it('can load file from custom pages endpoint', async () => {
|
|
config.pages.pagesEndpoint = 'pages';
|
|
await reconfigureServer(config);
|
|
|
|
const response = await request({
|
|
url: `http://localhost:8378/1/pages/email_verification_link_invalid.html`,
|
|
}).catch(e => e);
|
|
expect(response.status).toBe(200);
|
|
});
|
|
|
|
it('responds with 404 if publicServerURL is not configured', async () => {
|
|
await reconfigureServer({
|
|
appName: 'unused',
|
|
pages: { enableRouter: true },
|
|
});
|
|
const urls = [
|
|
'http://localhost:8378/1/apps/test/verify_email',
|
|
'http://localhost:8378/1/apps/choose_password?appId=test',
|
|
'http://localhost:8378/1/apps/test/request_password_reset',
|
|
];
|
|
for (const url of urls) {
|
|
const response = await request({ url }).catch(e => e);
|
|
expect(response.status).toBe(404);
|
|
}
|
|
});
|
|
|
|
it('responds with 403 access denied with invalid appId', async () => {
|
|
const reqs = [
|
|
{ url: 'http://localhost:8378/1/apps/invalid/verify_email', method: 'GET' },
|
|
{ url: 'http://localhost:8378/1/apps/choose_password?id=invalid', method: 'GET' },
|
|
{ url: 'http://localhost:8378/1/apps/invalid/request_password_reset', method: 'GET' },
|
|
{ url: 'http://localhost:8378/1/apps/invalid/request_password_reset', method: 'POST' },
|
|
{ url: 'http://localhost:8378/1/apps/invalid/resend_verification_email', method: 'POST' },
|
|
];
|
|
for (const req of reqs) {
|
|
const response = await request(req).catch(e => e);
|
|
expect(response.status).toBe(403);
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('AJAX requests', () => {
|
|
beforeEach(async () => {
|
|
await reconfigureServer({
|
|
appName: 'exampleAppname',
|
|
publicServerURL: 'http://localhost:8378/1',
|
|
pages: { enableRouter: true },
|
|
});
|
|
});
|
|
|
|
it('request_password_reset: responds with AJAX success', async () => {
|
|
spyOn(UserController.prototype, 'updatePassword').and.callFake(() => Promise.resolve());
|
|
const res = await request({
|
|
method: 'POST',
|
|
url: 'http://localhost:8378/1/apps/test/request_password_reset',
|
|
body: `new_password=user1&token=43634643&username=username`,
|
|
headers: {
|
|
'Content-Type': 'application/x-www-form-urlencoded',
|
|
'X-Requested-With': 'XMLHttpRequest',
|
|
},
|
|
followRedirects: false,
|
|
}).catch(e => e);
|
|
expect(res.status).toBe(200);
|
|
expect(res.text).toEqual('"Password successfully reset"');
|
|
});
|
|
|
|
it('request_password_reset: responds with AJAX error on missing password', async () => {
|
|
try {
|
|
await request({
|
|
method: 'POST',
|
|
url: 'http://localhost:8378/1/apps/test/request_password_reset',
|
|
body: `new_password=&token=132414&username=Johnny`,
|
|
headers: {
|
|
'Content-Type': 'application/x-www-form-urlencoded',
|
|
'X-Requested-With': 'XMLHttpRequest',
|
|
},
|
|
followRedirects: false,
|
|
});
|
|
} catch (error) {
|
|
expect(error.status).not.toBe(302);
|
|
expect(error.text).toEqual('{"code":201,"error":"Missing password"}');
|
|
}
|
|
});
|
|
|
|
it('request_password_reset: responds with AJAX error on missing username', async () => {
|
|
try {
|
|
await request({
|
|
method: 'POST',
|
|
url: 'http://localhost:8378/1/apps/test/request_password_reset',
|
|
body: `new_password=user1&token=43634643&username=`,
|
|
headers: {
|
|
'Content-Type': 'application/x-www-form-urlencoded',
|
|
'X-Requested-With': 'XMLHttpRequest',
|
|
},
|
|
followRedirects: false,
|
|
});
|
|
} catch (error) {
|
|
expect(error.status).not.toBe(302);
|
|
expect(error.text).toEqual('{"code":200,"error":"Missing username"}');
|
|
}
|
|
});
|
|
|
|
it('request_password_reset: responds with AJAX error on missing token', async () => {
|
|
try {
|
|
await request({
|
|
method: 'POST',
|
|
url: 'http://localhost:8378/1/apps/test/request_password_reset',
|
|
body: `new_password=user1&token=&username=Johnny`,
|
|
headers: {
|
|
'Content-Type': 'application/x-www-form-urlencoded',
|
|
'X-Requested-With': 'XMLHttpRequest',
|
|
},
|
|
followRedirects: false,
|
|
});
|
|
} catch (error) {
|
|
expect(error.status).not.toBe(302);
|
|
expect(error.text).toEqual('{"code":-1,"error":"Missing token"}');
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('pages', () => {
|
|
let router = new PagesRouter();
|
|
let req;
|
|
let config;
|
|
let goToPage;
|
|
let pageResponse;
|
|
let redirectResponse;
|
|
let readFile;
|
|
let exampleLocale;
|
|
|
|
const fillPlaceholders = (text, fill) => text.replace(/({{2,3}.*?}{2,3})/g, fill);
|
|
async function reconfigureServerWithPagesConfig(pagesConfig) {
|
|
config.pages = pagesConfig;
|
|
await reconfigureServer(config);
|
|
}
|
|
|
|
beforeEach(async () => {
|
|
router = new PagesRouter();
|
|
readFile = spyOn(fs, 'readFile').and.callThrough();
|
|
goToPage = spyOn(PagesRouter.prototype, 'goToPage').and.callThrough();
|
|
pageResponse = spyOn(PagesRouter.prototype, 'pageResponse').and.callThrough();
|
|
redirectResponse = spyOn(PagesRouter.prototype, 'redirectResponse').and.callThrough();
|
|
exampleLocale = 'de-AT';
|
|
config = {
|
|
appId: 'test',
|
|
appName: 'ExampleAppName',
|
|
verifyUserEmails: true,
|
|
emailAdapter: {
|
|
sendVerificationEmail: () => Promise.resolve(),
|
|
sendPasswordResetEmail: () => Promise.resolve(),
|
|
sendMail: () => {},
|
|
},
|
|
publicServerURL: 'http://localhost:8378/1',
|
|
pages: {
|
|
enableRouter: true,
|
|
enableLocalization: true,
|
|
customUrls: {},
|
|
},
|
|
};
|
|
req = {
|
|
method: 'GET',
|
|
config,
|
|
query: {
|
|
locale: exampleLocale,
|
|
},
|
|
};
|
|
});
|
|
|
|
describe('server options', () => {
|
|
it('uses default configuration when none is set', async () => {
|
|
await reconfigureServerWithPagesConfig({});
|
|
expect(Config.get(Parse.applicationId).pages.enableRouter).toBe(
|
|
Definitions.PagesOptions.enableRouter.default
|
|
);
|
|
expect(Config.get(Parse.applicationId).pages.enableLocalization).toBe(
|
|
Definitions.PagesOptions.enableLocalization.default
|
|
);
|
|
expect(Config.get(Parse.applicationId).pages.localizationJsonPath).toBe(
|
|
Definitions.PagesOptions.localizationJsonPath.default
|
|
);
|
|
expect(Config.get(Parse.applicationId).pages.localizationFallbackLocale).toBe(
|
|
Definitions.PagesOptions.localizationFallbackLocale.default
|
|
);
|
|
expect(Config.get(Parse.applicationId).pages.placeholders).toBe(
|
|
Definitions.PagesOptions.placeholders.default
|
|
);
|
|
expect(Config.get(Parse.applicationId).pages.forceRedirect).toBe(
|
|
Definitions.PagesOptions.forceRedirect.default
|
|
);
|
|
expect(Config.get(Parse.applicationId).pages.pagesPath).toBe(
|
|
Definitions.PagesOptions.pagesPath.default
|
|
);
|
|
expect(Config.get(Parse.applicationId).pages.pagesEndpoint).toBe(
|
|
Definitions.PagesOptions.pagesEndpoint.default
|
|
);
|
|
expect(Config.get(Parse.applicationId).pages.customUrls).toBe(
|
|
Definitions.PagesOptions.customUrls.default
|
|
);
|
|
expect(Config.get(Parse.applicationId).pages.customRoutes).toBe(
|
|
Definitions.PagesOptions.customRoutes.default
|
|
);
|
|
});
|
|
|
|
it('throws on invalid configuration', async () => {
|
|
const options = [
|
|
[],
|
|
'a',
|
|
0,
|
|
true,
|
|
{ enableRouter: 'a' },
|
|
{ enableRouter: 0 },
|
|
{ enableRouter: {} },
|
|
{ enableRouter: [] },
|
|
{ enableLocalization: 'a' },
|
|
{ enableLocalization: 0 },
|
|
{ enableLocalization: {} },
|
|
{ enableLocalization: [] },
|
|
{ forceRedirect: 'a' },
|
|
{ forceRedirect: 0 },
|
|
{ forceRedirect: {} },
|
|
{ forceRedirect: [] },
|
|
{ placeholders: true },
|
|
{ placeholders: 'a' },
|
|
{ placeholders: 0 },
|
|
{ placeholders: [] },
|
|
{ pagesPath: true },
|
|
{ pagesPath: 0 },
|
|
{ pagesPath: {} },
|
|
{ pagesPath: [] },
|
|
{ pagesEndpoint: true },
|
|
{ pagesEndpoint: 0 },
|
|
{ pagesEndpoint: {} },
|
|
{ pagesEndpoint: [] },
|
|
{ customUrls: true },
|
|
{ customUrls: 0 },
|
|
{ customUrls: 'a' },
|
|
{ customUrls: [] },
|
|
{ localizationJsonPath: true },
|
|
{ localizationJsonPath: 0 },
|
|
{ localizationJsonPath: {} },
|
|
{ localizationJsonPath: [] },
|
|
{ localizationFallbackLocale: true },
|
|
{ localizationFallbackLocale: 0 },
|
|
{ localizationFallbackLocale: {} },
|
|
{ localizationFallbackLocale: [] },
|
|
{ customRoutes: true },
|
|
{ customRoutes: 0 },
|
|
{ customRoutes: 'a' },
|
|
{ customRoutes: {} },
|
|
];
|
|
for (const option of options) {
|
|
await expectAsync(reconfigureServerWithPagesConfig(option)).toBeRejected();
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('placeholders', () => {
|
|
it('replaces placeholder in response content', async () => {
|
|
await expectAsync(router.goToPage(req, pages.passwordResetLinkInvalid)).toBeResolved();
|
|
|
|
expect(readFile.calls.all()[0].returnValue).toBeDefined();
|
|
const originalContent = await readFile.calls.all()[0].returnValue;
|
|
expect(originalContent).toContain('{{appName}}');
|
|
|
|
expect(pageResponse.calls.all()[0].returnValue).toBeDefined();
|
|
const replacedContent = await pageResponse.calls.all()[0].returnValue;
|
|
expect(replacedContent.text).not.toContain('{{appName}}');
|
|
expect(replacedContent.text).toContain(req.config.appName);
|
|
});
|
|
|
|
it('removes undefined placeholder in response content', async () => {
|
|
await expectAsync(router.goToPage(req, pages.passwordReset)).toBeResolved();
|
|
|
|
expect(readFile.calls.all()[0].returnValue).toBeDefined();
|
|
const originalContent = await readFile.calls.all()[0].returnValue;
|
|
expect(originalContent).toContain('{{error}}');
|
|
|
|
// There is no error placeholder value set by default, so the
|
|
// {{error}} placeholder should just be removed from content
|
|
expect(pageResponse.calls.all()[0].returnValue).toBeDefined();
|
|
const replacedContent = await pageResponse.calls.all()[0].returnValue;
|
|
expect(replacedContent.text).not.toContain('{{error}}');
|
|
});
|
|
|
|
it('fills placeholders from config object', async () => {
|
|
config.pages.enableLocalization = false;
|
|
config.pages.placeholders = {
|
|
title: 'setViaConfig',
|
|
};
|
|
await reconfigureServer(config);
|
|
const response = await request({
|
|
url: 'http://localhost:8378/1/apps/custom_json.html',
|
|
followRedirects: false,
|
|
method: 'GET',
|
|
});
|
|
expect(response.status).toEqual(200);
|
|
expect(response.text).toContain(config.pages.placeholders.title);
|
|
});
|
|
|
|
it('fills placeholders from config function', async () => {
|
|
config.pages.enableLocalization = false;
|
|
config.pages.placeholders = () => {
|
|
return { title: 'setViaConfig' };
|
|
};
|
|
await reconfigureServer(config);
|
|
const response = await request({
|
|
url: 'http://localhost:8378/1/apps/custom_json.html',
|
|
followRedirects: false,
|
|
method: 'GET',
|
|
});
|
|
expect(response.status).toEqual(200);
|
|
expect(response.text).toContain(config.pages.placeholders().title);
|
|
});
|
|
|
|
it('fills placeholders from config promise', async () => {
|
|
config.pages.enableLocalization = false;
|
|
config.pages.placeholders = async () => {
|
|
return { title: 'setViaConfig' };
|
|
};
|
|
await reconfigureServer(config);
|
|
const response = await request({
|
|
url: 'http://localhost:8378/1/apps/custom_json.html',
|
|
followRedirects: false,
|
|
method: 'GET',
|
|
});
|
|
expect(response.status).toEqual(200);
|
|
expect(response.text).toContain((await config.pages.placeholders()).title);
|
|
});
|
|
});
|
|
|
|
describe('localization', () => {
|
|
it('returns default file if localization is disabled', async () => {
|
|
delete req.config.pages.enableLocalization;
|
|
|
|
await expectAsync(router.goToPage(req, pages.passwordResetLinkInvalid)).toBeResolved();
|
|
expect(pageResponse.calls.all()[0].args[0]).toBeDefined();
|
|
expect(pageResponse.calls.all()[0].args[0]).not.toMatch(
|
|
new RegExp(`\/de(-AT)?\/${pages.passwordResetLinkInvalid.defaultFile}`)
|
|
);
|
|
});
|
|
|
|
it('returns default file if no locale is specified', async () => {
|
|
delete req.query.locale;
|
|
|
|
await expectAsync(router.goToPage(req, pages.passwordResetLinkInvalid)).toBeResolved();
|
|
expect(pageResponse.calls.all()[0].args[0]).toBeDefined();
|
|
expect(pageResponse.calls.all()[0].args[0]).not.toMatch(
|
|
new RegExp(`\/de(-AT)?\/${pages.passwordResetLinkInvalid.defaultFile}`)
|
|
);
|
|
});
|
|
|
|
it('returns custom page regardless of localization enabled', async () => {
|
|
req.config.pages.customUrls = {
|
|
passwordResetLinkInvalid: 'http://invalid-link.example.com',
|
|
};
|
|
|
|
await expectAsync(router.goToPage(req, pages.passwordResetLinkInvalid)).toBeResolved();
|
|
expect(pageResponse).not.toHaveBeenCalled();
|
|
expect(redirectResponse.calls.all()[0].args[0]).toBe(
|
|
req.config.pages.customUrls.passwordResetLinkInvalid
|
|
);
|
|
});
|
|
|
|
it('returns file for locale match', async () => {
|
|
await expectAsync(router.goToPage(req, pages.passwordResetLinkInvalid)).toBeResolved();
|
|
expect(pageResponse.calls.all()[0].args[0]).toBeDefined();
|
|
expect(pageResponse.calls.all()[0].args[0]).toMatch(
|
|
new RegExp(`\/${req.query.locale}\/${pages.passwordResetLinkInvalid.defaultFile}`)
|
|
);
|
|
});
|
|
|
|
it('returns file for language match', async () => {
|
|
// Pretend no locale matching file exists
|
|
spyOn(Utils, 'fileExists').and.callFake(async path => {
|
|
return !path.includes(
|
|
`/${req.query.locale}/${pages.passwordResetLinkInvalid.defaultFile}`
|
|
);
|
|
});
|
|
|
|
await expectAsync(router.goToPage(req, pages.passwordResetLinkInvalid)).toBeResolved();
|
|
expect(pageResponse.calls.all()[0].args[0]).toBeDefined();
|
|
expect(pageResponse.calls.all()[0].args[0]).toMatch(
|
|
new RegExp(`\/de\/${pages.passwordResetLinkInvalid.defaultFile}`)
|
|
);
|
|
});
|
|
|
|
it('returns default file for neither locale nor language match', async () => {
|
|
req.query.locale = 'yo-LO';
|
|
|
|
await expectAsync(router.goToPage(req, pages.passwordResetLinkInvalid)).toBeResolved();
|
|
expect(pageResponse.calls.all()[0].args[0]).toBeDefined();
|
|
expect(pageResponse.calls.all()[0].args[0]).not.toMatch(
|
|
new RegExp(`\/yo(-LO)?\/${pages.passwordResetLinkInvalid.defaultFile}`)
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('localization with JSON resource', () => {
|
|
let jsonPageFile;
|
|
let jsonPageUrl;
|
|
let jsonResource;
|
|
|
|
beforeEach(async () => {
|
|
jsonPageFile = 'custom_json.html';
|
|
jsonPageUrl = new URL(`${config.publicServerURL}/apps/${jsonPageFile}`);
|
|
jsonResource = require('../public/custom_json.json');
|
|
|
|
config.pages.enableLocalization = true;
|
|
config.pages.localizationJsonPath = './public/custom_json.json';
|
|
config.pages.localizationFallbackLocale = 'en';
|
|
await reconfigureServer(config);
|
|
});
|
|
|
|
it('does not localize with JSON resource if localization is disabled', async () => {
|
|
config.pages.enableLocalization = false;
|
|
config.pages.localizationJsonPath = './public/custom_json.json';
|
|
config.pages.localizationFallbackLocale = 'en';
|
|
await reconfigureServer(config);
|
|
|
|
const response = await request({
|
|
url: jsonPageUrl.toString(),
|
|
followRedirects: false,
|
|
}).catch(e => e);
|
|
expect(response.status).toBe(200);
|
|
expect(pageResponse.calls.all()[0].args[1]).toEqual({});
|
|
expect(pageResponse.calls.all()[0].args[2]).toEqual({});
|
|
|
|
// Ensure header contains no page params
|
|
const pageParamHeaders = Object.keys(response.headers).filter(header =>
|
|
header.startsWith(pageParamHeaderPrefix)
|
|
);
|
|
expect(pageParamHeaders.length).toBe(0);
|
|
|
|
// Ensure page response does not contain any translation
|
|
const flattenedJson = Utils.flattenObject(jsonResource);
|
|
for (const value of Object.values(flattenedJson)) {
|
|
const valueWithoutPlaceholder = fillPlaceholders(value, '');
|
|
expect(response.text).not.toContain(valueWithoutPlaceholder);
|
|
}
|
|
});
|
|
|
|
it('localizes static page with JSON resource and fallback locale', async () => {
|
|
const response = await request({
|
|
url: jsonPageUrl.toString(),
|
|
followRedirects: false,
|
|
}).catch(e => e);
|
|
expect(response.status).toBe(200);
|
|
|
|
// Ensure page response contains translation of fallback locale
|
|
const translation = jsonResource[config.pages.localizationFallbackLocale].translation;
|
|
for (const value of Object.values(translation)) {
|
|
const valueWithoutPlaceholder = fillPlaceholders(value, '');
|
|
expect(response.text).toContain(valueWithoutPlaceholder);
|
|
}
|
|
});
|
|
|
|
it('localizes static page with JSON resource and request locale', async () => {
|
|
// Add locale to request URL
|
|
jsonPageUrl.searchParams.set('locale', exampleLocale);
|
|
|
|
const response = await request({
|
|
url: jsonPageUrl.toString(),
|
|
followRedirects: false,
|
|
}).catch(e => e);
|
|
expect(response.status).toBe(200);
|
|
|
|
// Ensure page response contains translations of request locale
|
|
const translation = jsonResource[exampleLocale].translation;
|
|
for (const value of Object.values(translation)) {
|
|
const valueWithoutPlaceholder = fillPlaceholders(value, '');
|
|
expect(response.text).toContain(valueWithoutPlaceholder);
|
|
}
|
|
});
|
|
|
|
it('localizes static page with JSON resource and language matching request locale', async () => {
|
|
// Add locale to request URL that has no locale match but only a language
|
|
// match in the JSON resource
|
|
jsonPageUrl.searchParams.set('locale', 'de-CH');
|
|
|
|
const response = await request({
|
|
url: jsonPageUrl.toString(),
|
|
followRedirects: false,
|
|
}).catch(e => e);
|
|
expect(response.status).toBe(200);
|
|
|
|
// Ensure page response contains translations of requst language
|
|
const translation = jsonResource['de'].translation;
|
|
for (const value of Object.values(translation)) {
|
|
const valueWithoutPlaceholder = fillPlaceholders(value, '');
|
|
expect(response.text).toContain(valueWithoutPlaceholder);
|
|
}
|
|
});
|
|
|
|
it('localizes static page with JSON resource and fills placeholders in JSON values', async () => {
|
|
// Add app ID to request URL so that the request is assigned to a Parse Server app
|
|
// and placeholders within translations strings can be replaced with default page
|
|
// parameters such as `appId`
|
|
jsonPageUrl.searchParams.set('appId', config.appId);
|
|
jsonPageUrl.searchParams.set('locale', exampleLocale);
|
|
|
|
const response = await request({
|
|
url: jsonPageUrl.toString(),
|
|
followRedirects: false,
|
|
}).catch(e => e);
|
|
expect(response.status).toBe(200);
|
|
|
|
// Fill placeholders in transation
|
|
let translation = jsonResource[exampleLocale].translation;
|
|
translation = JSON.stringify(translation);
|
|
translation = mustache.render(translation, { appName: config.appName });
|
|
translation = JSON.parse(translation);
|
|
|
|
// Ensure page response contains translation of request locale
|
|
for (const value of Object.values(translation)) {
|
|
expect(response.text).toContain(value);
|
|
}
|
|
});
|
|
|
|
it('localizes feature page with JSON resource and fills placeholders in JSON values', async () => {
|
|
// Fake any page to load the JSON page file
|
|
spyOnProperty(Page.prototype, 'defaultFile').and.returnValue(jsonPageFile);
|
|
|
|
const response = await request({
|
|
url: `http://localhost:8378/1/apps/test/request_password_reset?token=exampleToken&username=exampleUsername&locale=${exampleLocale}`,
|
|
followRedirects: false,
|
|
}).catch(e => e);
|
|
expect(response.status).toEqual(200);
|
|
|
|
// Fill placeholders in transation
|
|
let translation = jsonResource[exampleLocale].translation;
|
|
translation = JSON.stringify(translation);
|
|
translation = mustache.render(translation, { appName: config.appName });
|
|
translation = JSON.parse(translation);
|
|
|
|
// Ensure page response contains translation of request locale
|
|
for (const value of Object.values(translation)) {
|
|
expect(response.text).toContain(value);
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('response type', () => {
|
|
it('returns a file for GET request', async () => {
|
|
await expectAsync(router.goToPage(req, pages.passwordResetLinkInvalid)).toBeResolved();
|
|
expect(pageResponse).toHaveBeenCalled();
|
|
expect(redirectResponse).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('returns a redirect for POST request', async () => {
|
|
req.method = 'POST';
|
|
await expectAsync(router.goToPage(req, pages.passwordResetLinkInvalid)).toBeResolved();
|
|
expect(pageResponse).not.toHaveBeenCalled();
|
|
expect(redirectResponse).toHaveBeenCalled();
|
|
});
|
|
|
|
it('returns a redirect for custom pages for GET and POST request', async () => {
|
|
req.config.pages.customUrls = {
|
|
passwordResetLinkInvalid: 'http://invalid-link.example.com',
|
|
};
|
|
|
|
for (const method of ['GET', 'POST']) {
|
|
req.method = method;
|
|
await expectAsync(router.goToPage(req, pages.passwordResetLinkInvalid)).toBeResolved();
|
|
expect(pageResponse).not.toHaveBeenCalled();
|
|
expect(redirectResponse).toHaveBeenCalled();
|
|
}
|
|
});
|
|
|
|
it('responds to POST request with redirect response', async () => {
|
|
await reconfigureServer(config);
|
|
const response = await request({
|
|
url:
|
|
'http://localhost:8378/1/apps/test/request_password_reset?token=exampleToken&username=exampleUsername&locale=de-AT',
|
|
followRedirects: false,
|
|
method: 'POST',
|
|
});
|
|
expect(response.status).toEqual(303);
|
|
expect(response.headers.location).toContain(
|
|
'http://localhost:8378/1/apps/de-AT/password_reset_link_invalid.html'
|
|
);
|
|
});
|
|
|
|
it('responds to GET request with content response', async () => {
|
|
await reconfigureServer(config);
|
|
const response = await request({
|
|
url:
|
|
'http://localhost:8378/1/apps/test/request_password_reset?token=exampleToken&username=exampleUsername&locale=de-AT',
|
|
followRedirects: false,
|
|
method: 'GET',
|
|
});
|
|
expect(response.status).toEqual(200);
|
|
expect(response.text).toContain('<html>');
|
|
});
|
|
});
|
|
|
|
describe('end-to-end tests', () => {
|
|
it('localizes end-to-end for password reset: success', async () => {
|
|
await reconfigureServer(config);
|
|
const sendPasswordResetEmail = spyOn(
|
|
config.emailAdapter,
|
|
'sendPasswordResetEmail'
|
|
).and.callThrough();
|
|
const user = new Parse.User();
|
|
user.setUsername('exampleUsername');
|
|
user.setPassword('examplePassword');
|
|
user.set('email', 'mail@example.com');
|
|
await user.signUp();
|
|
await Parse.User.requestPasswordReset(user.getEmail());
|
|
|
|
const link = sendPasswordResetEmail.calls.all()[0].args[0].link;
|
|
const linkWithLocale = new URL(link);
|
|
linkWithLocale.searchParams.append(pageParams.locale, exampleLocale);
|
|
|
|
const linkResponse = await request({
|
|
url: linkWithLocale.toString(),
|
|
followRedirects: false,
|
|
});
|
|
expect(linkResponse.status).toBe(200);
|
|
|
|
const appId = linkResponse.headers['x-parse-page-param-appid'];
|
|
const token = linkResponse.headers['x-parse-page-param-token'];
|
|
const locale = linkResponse.headers['x-parse-page-param-locale'];
|
|
const username = linkResponse.headers['x-parse-page-param-username'];
|
|
const publicServerUrl = linkResponse.headers['x-parse-page-param-publicserverurl'];
|
|
const passwordResetPagePath = pageResponse.calls.all()[0].args[0];
|
|
expect(appId).toBeDefined();
|
|
expect(token).toBeDefined();
|
|
expect(locale).toBeDefined();
|
|
expect(username).toBeDefined();
|
|
expect(publicServerUrl).toBeDefined();
|
|
expect(passwordResetPagePath).toMatch(
|
|
new RegExp(`\/${exampleLocale}\/${pages.passwordReset.defaultFile}`)
|
|
);
|
|
pageResponse.calls.reset();
|
|
|
|
const formUrl = `${publicServerUrl}/apps/${appId}/request_password_reset`;
|
|
const formResponse = await request({
|
|
url: formUrl,
|
|
method: 'POST',
|
|
body: {
|
|
token,
|
|
locale,
|
|
username,
|
|
new_password: 'newPassword',
|
|
},
|
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
followRedirects: false,
|
|
});
|
|
expect(formResponse.status).toEqual(200);
|
|
expect(pageResponse.calls.all()[0].args[0]).toContain(
|
|
`/${locale}/${pages.passwordResetSuccess.defaultFile}`
|
|
);
|
|
});
|
|
|
|
it('localizes end-to-end for password reset: invalid link', async () => {
|
|
await reconfigureServer(config);
|
|
const sendPasswordResetEmail = spyOn(
|
|
config.emailAdapter,
|
|
'sendPasswordResetEmail'
|
|
).and.callThrough();
|
|
const user = new Parse.User();
|
|
user.setUsername('exampleUsername');
|
|
user.setPassword('examplePassword');
|
|
user.set('email', 'mail@example.com');
|
|
await user.signUp();
|
|
await Parse.User.requestPasswordReset(user.getEmail());
|
|
|
|
const link = sendPasswordResetEmail.calls.all()[0].args[0].link;
|
|
const linkWithLocale = new URL(link);
|
|
linkWithLocale.searchParams.append(pageParams.locale, exampleLocale);
|
|
linkWithLocale.searchParams.set(pageParams.token, 'invalidToken');
|
|
|
|
const linkResponse = await request({
|
|
url: linkWithLocale.toString(),
|
|
followRedirects: false,
|
|
});
|
|
expect(linkResponse.status).toBe(200);
|
|
|
|
const pagePath = pageResponse.calls.all()[0].args[0];
|
|
expect(pagePath).toMatch(
|
|
new RegExp(`\/${exampleLocale}\/${pages.passwordResetLinkInvalid.defaultFile}`)
|
|
);
|
|
});
|
|
|
|
it_id('2845c2ea-23ba-45d2-a33f-63181d419bca')(it)('localizes end-to-end for verify email: success', async () => {
|
|
await reconfigureServer(config);
|
|
const sendVerificationEmail = spyOn(
|
|
config.emailAdapter,
|
|
'sendVerificationEmail'
|
|
).and.callThrough();
|
|
const user = new Parse.User();
|
|
user.setUsername('exampleUsername');
|
|
user.setPassword('examplePassword');
|
|
user.set('email', 'mail@example.com');
|
|
await user.signUp();
|
|
await jasmine.timeout();
|
|
|
|
const link = sendVerificationEmail.calls.all()[0].args[0].link;
|
|
const linkWithLocale = new URL(link);
|
|
linkWithLocale.searchParams.append(pageParams.locale, exampleLocale);
|
|
|
|
const linkResponse = await request({
|
|
url: linkWithLocale.toString(),
|
|
followRedirects: false,
|
|
});
|
|
expect(linkResponse.status).toBe(200);
|
|
|
|
const pagePath = pageResponse.calls.all()[0].args[0];
|
|
expect(pagePath).toMatch(
|
|
new RegExp(`\/${exampleLocale}\/${pages.emailVerificationSuccess.defaultFile}`)
|
|
);
|
|
});
|
|
|
|
it_id('f2272b94-b4ac-474f-8e47-1ca74de136f5')(it)('localizes end-to-end for verify email: invalid verification link - link send success', async () => {
|
|
await reconfigureServer(config);
|
|
const sendVerificationEmail = spyOn(
|
|
config.emailAdapter,
|
|
'sendVerificationEmail'
|
|
).and.callThrough();
|
|
const user = new Parse.User();
|
|
user.setUsername('exampleUsername');
|
|
user.setPassword('examplePassword');
|
|
user.set('email', 'mail@example.com');
|
|
await user.signUp();
|
|
await jasmine.timeout();
|
|
|
|
const link = sendVerificationEmail.calls.all()[0].args[0].link;
|
|
const linkWithLocale = new URL(link);
|
|
linkWithLocale.searchParams.append(pageParams.locale, exampleLocale);
|
|
linkWithLocale.searchParams.set(pageParams.token, 'invalidToken');
|
|
|
|
const linkResponse = await request({
|
|
url: linkWithLocale.toString(),
|
|
followRedirects: false,
|
|
});
|
|
expect(linkResponse.status).toBe(200);
|
|
|
|
const appId = linkResponse.headers['x-parse-page-param-appid'];
|
|
const locale = linkResponse.headers['x-parse-page-param-locale'];
|
|
const username = linkResponse.headers['x-parse-page-param-username'];
|
|
const publicServerUrl = linkResponse.headers['x-parse-page-param-publicserverurl'];
|
|
const invalidVerificationPagePath = pageResponse.calls.all()[0].args[0];
|
|
expect(appId).toBeDefined();
|
|
expect(locale).toBe(exampleLocale);
|
|
expect(username).toBeDefined();
|
|
expect(publicServerUrl).toBeDefined();
|
|
expect(invalidVerificationPagePath).toMatch(
|
|
new RegExp(`\/${exampleLocale}\/${pages.emailVerificationLinkExpired.defaultFile}`)
|
|
);
|
|
|
|
const formUrl = `${publicServerUrl}/apps/${appId}/resend_verification_email`;
|
|
const formResponse = await request({
|
|
url: formUrl,
|
|
method: 'POST',
|
|
body: {
|
|
locale,
|
|
username,
|
|
},
|
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
followRedirects: false,
|
|
});
|
|
expect(formResponse.status).toEqual(303);
|
|
expect(formResponse.text).toContain(
|
|
`/${locale}/${pages.emailVerificationSendSuccess.defaultFile}`
|
|
);
|
|
});
|
|
|
|
it_id('1d46d36a-e455-4ae7-8717-e0d286e95f02')(it)('localizes end-to-end for verify email: invalid verification link - link send fail', async () => {
|
|
await reconfigureServer(config);
|
|
const sendVerificationEmail = spyOn(
|
|
config.emailAdapter,
|
|
'sendVerificationEmail'
|
|
).and.callThrough();
|
|
const user = new Parse.User();
|
|
user.setUsername('exampleUsername');
|
|
user.setPassword('examplePassword');
|
|
user.set('email', 'mail@example.com');
|
|
await user.signUp();
|
|
await jasmine.timeout();
|
|
|
|
const link = sendVerificationEmail.calls.all()[0].args[0].link;
|
|
const linkWithLocale = new URL(link);
|
|
linkWithLocale.searchParams.append(pageParams.locale, exampleLocale);
|
|
linkWithLocale.searchParams.set(pageParams.token, 'invalidToken');
|
|
|
|
const linkResponse = await request({
|
|
url: linkWithLocale.toString(),
|
|
followRedirects: false,
|
|
});
|
|
expect(linkResponse.status).toBe(200);
|
|
|
|
const appId = linkResponse.headers['x-parse-page-param-appid'];
|
|
const locale = linkResponse.headers['x-parse-page-param-locale'];
|
|
const username = linkResponse.headers['x-parse-page-param-username'];
|
|
const publicServerUrl = linkResponse.headers['x-parse-page-param-publicserverurl'];
|
|
await jasmine.timeout();
|
|
|
|
const invalidVerificationPagePath = pageResponse.calls.all()[0].args[0];
|
|
expect(appId).toBeDefined();
|
|
expect(locale).toBe(exampleLocale);
|
|
expect(username).toBeDefined();
|
|
expect(publicServerUrl).toBeDefined();
|
|
expect(invalidVerificationPagePath).toMatch(
|
|
new RegExp(`\/${exampleLocale}\/${pages.emailVerificationLinkExpired.defaultFile}`)
|
|
);
|
|
|
|
spyOn(UserController.prototype, 'resendVerificationEmail').and.callFake(() =>
|
|
Promise.reject('failed to resend verification email')
|
|
);
|
|
|
|
const formUrl = `${publicServerUrl}/apps/${appId}/resend_verification_email`;
|
|
const formResponse = await request({
|
|
url: formUrl,
|
|
method: 'POST',
|
|
body: {
|
|
locale,
|
|
username,
|
|
},
|
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
followRedirects: false,
|
|
});
|
|
expect(formResponse.status).toEqual(303);
|
|
expect(formResponse.text).toContain(
|
|
`/${locale}/${pages.emailVerificationSendFail.defaultFile}`
|
|
);
|
|
});
|
|
|
|
it('localizes end-to-end for resend verification email: invalid link', async () => {
|
|
await reconfigureServer(config);
|
|
const formUrl = `${config.publicServerURL}/apps/${config.appId}/resend_verification_email`;
|
|
const formResponse = await request({
|
|
url: formUrl,
|
|
method: 'POST',
|
|
body: {
|
|
locale: exampleLocale,
|
|
},
|
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
followRedirects: false,
|
|
});
|
|
expect(formResponse.status).toEqual(303);
|
|
expect(formResponse.text).toContain(
|
|
`/${exampleLocale}/${pages.emailVerificationLinkInvalid.defaultFile}`
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('failing with missing parameters', () => {
|
|
it('verifyEmail: throws on missing server configuration', async () => {
|
|
delete req.config;
|
|
const verifyEmail = req => (() => new PagesRouter().verifyEmail(req)).bind(null);
|
|
expect(verifyEmail(req)).toThrow();
|
|
});
|
|
|
|
it('resendVerificationEmail: throws on missing server configuration', async () => {
|
|
delete req.config;
|
|
const resendVerificationEmail = req =>
|
|
(() => new PagesRouter().resendVerificationEmail(req)).bind(null);
|
|
expect(resendVerificationEmail(req)).toThrow();
|
|
});
|
|
|
|
it('requestResetPassword: throws on missing server configuration', async () => {
|
|
delete req.config;
|
|
const requestResetPassword = req =>
|
|
(() => new PagesRouter().requestResetPassword(req)).bind(null);
|
|
expect(requestResetPassword(req)).toThrow();
|
|
});
|
|
|
|
it('resetPassword: throws on missing server configuration', async () => {
|
|
delete req.config;
|
|
const resetPassword = req => (() => new PagesRouter().resetPassword(req)).bind(null);
|
|
expect(resetPassword(req)).toThrow();
|
|
});
|
|
|
|
it('verifyEmail: responds with invalid link on missing username', async () => {
|
|
req.query.token = 'exampleToken';
|
|
req.params = {};
|
|
req.config.userController = { verifyEmail: () => Promise.reject() };
|
|
const verifyEmail = req => new PagesRouter().verifyEmail(req);
|
|
|
|
await verifyEmail(req);
|
|
expect(goToPage.calls.all()[0].args[1]).toBe(pages.emailVerificationLinkInvalid);
|
|
});
|
|
|
|
it('resetPassword: responds with page choose password with error message on failed password update', async () => {
|
|
req.body = {
|
|
token: 'exampleToken',
|
|
username: 'exampleUsername',
|
|
new_password: 'examplePassword',
|
|
};
|
|
const error = 'exampleError';
|
|
req.config.userController = { updatePassword: () => Promise.reject(error) };
|
|
const resetPassword = req => new PagesRouter().resetPassword(req);
|
|
|
|
await resetPassword(req);
|
|
expect(goToPage.calls.all()[0].args[1]).toBe(pages.passwordReset);
|
|
expect(goToPage.calls.all()[0].args[2].error).toBe(error);
|
|
});
|
|
|
|
it('resetPassword: responds with AJAX error with error message on failed password update', async () => {
|
|
req.xhr = true;
|
|
req.body = {
|
|
token: 'exampleToken',
|
|
username: 'exampleUsername',
|
|
new_password: 'examplePassword',
|
|
};
|
|
const error = 'exampleError';
|
|
req.config.userController = { updatePassword: () => Promise.reject(error) };
|
|
const resetPassword = req => new PagesRouter().resetPassword(req).catch(e => e);
|
|
|
|
const response = await resetPassword(req);
|
|
expect(response.code).toBe(Parse.Error.OTHER_CAUSE);
|
|
});
|
|
});
|
|
|
|
describe('exploits', () => {
|
|
it('rejects requesting file outside of pages scope with UNIX path patterns', async () => {
|
|
await reconfigureServer(config);
|
|
|
|
// Do not compose this URL with `new URL(...)` because that would normalize
|
|
// the URL and remove path patterns; the path patterns must reach the router
|
|
const url = `${config.publicServerURL}/apps/../.gitignore`;
|
|
const response = await request({
|
|
url: url,
|
|
followRedirects: false,
|
|
}).catch(e => e);
|
|
expect(response.status).toBe(404);
|
|
expect(response.text).toBe('Not found.');
|
|
});
|
|
});
|
|
|
|
describe('custom route', () => {
|
|
it('handles custom route with GET', async () => {
|
|
config.pages.customRoutes = [
|
|
{
|
|
method: 'GET',
|
|
path: 'custom_page',
|
|
handler: async req => {
|
|
expect(req).toBeDefined();
|
|
expect(req.method).toBe('GET');
|
|
return { file: 'custom_page.html' };
|
|
},
|
|
},
|
|
];
|
|
await reconfigureServer(config);
|
|
const handlerSpy = spyOn(config.pages.customRoutes[0], 'handler').and.callThrough();
|
|
|
|
const url = `${config.publicServerURL}/apps/${config.appId}/custom_page`;
|
|
const response = await request({
|
|
url: url,
|
|
followRedirects: false,
|
|
}).catch(e => e);
|
|
expect(response.status).toBe(200);
|
|
expect(response.text).toMatch(config.appName);
|
|
expect(handlerSpy).toHaveBeenCalled();
|
|
});
|
|
|
|
it('handles custom route with POST', async () => {
|
|
config.pages.customRoutes = [
|
|
{
|
|
method: 'POST',
|
|
path: 'custom_page',
|
|
handler: async req => {
|
|
expect(req).toBeDefined();
|
|
expect(req.method).toBe('POST');
|
|
return { file: 'custom_page.html' };
|
|
},
|
|
},
|
|
];
|
|
const handlerSpy = spyOn(config.pages.customRoutes[0], 'handler').and.callThrough();
|
|
await reconfigureServer(config);
|
|
|
|
const url = `${config.publicServerURL}/apps/${config.appId}/custom_page`;
|
|
const response = await request({
|
|
url: url,
|
|
followRedirects: false,
|
|
method: 'POST',
|
|
}).catch(e => e);
|
|
expect(response.status).toBe(200);
|
|
expect(response.text).toMatch(config.appName);
|
|
expect(handlerSpy).toHaveBeenCalled();
|
|
});
|
|
|
|
it('handles multiple custom routes', async () => {
|
|
config.pages.customRoutes = [
|
|
{
|
|
method: 'GET',
|
|
path: 'custom_page',
|
|
handler: async req => {
|
|
expect(req).toBeDefined();
|
|
expect(req.method).toBe('GET');
|
|
return { file: 'custom_page.html' };
|
|
},
|
|
},
|
|
{
|
|
method: 'POST',
|
|
path: 'custom_page',
|
|
handler: async req => {
|
|
expect(req).toBeDefined();
|
|
expect(req.method).toBe('POST');
|
|
return { file: 'custom_page.html' };
|
|
},
|
|
},
|
|
];
|
|
const getHandlerSpy = spyOn(config.pages.customRoutes[0], 'handler').and.callThrough();
|
|
const postHandlerSpy = spyOn(config.pages.customRoutes[1], 'handler').and.callThrough();
|
|
await reconfigureServer(config);
|
|
|
|
const url = `${config.publicServerURL}/apps/${config.appId}/custom_page`;
|
|
const getResponse = await request({
|
|
url: url,
|
|
followRedirects: false,
|
|
method: 'GET',
|
|
}).catch(e => e);
|
|
expect(getResponse.status).toBe(200);
|
|
expect(getResponse.text).toMatch(config.appName);
|
|
expect(getHandlerSpy).toHaveBeenCalled();
|
|
|
|
const postResponse = await request({
|
|
url: url,
|
|
followRedirects: false,
|
|
method: 'POST',
|
|
}).catch(e => e);
|
|
expect(postResponse.status).toBe(200);
|
|
expect(postResponse.text).toMatch(config.appName);
|
|
expect(postHandlerSpy).toHaveBeenCalled();
|
|
});
|
|
|
|
it('handles custom route with async handler', async () => {
|
|
config.pages.customRoutes = [
|
|
{
|
|
method: 'GET',
|
|
path: 'custom_page',
|
|
handler: async req => {
|
|
expect(req).toBeDefined();
|
|
expect(req.method).toBe('GET');
|
|
const file = await new Promise(resolve =>
|
|
setTimeout(resolve('custom_page.html'), 1000)
|
|
);
|
|
return { file };
|
|
},
|
|
},
|
|
];
|
|
await reconfigureServer(config);
|
|
const handlerSpy = spyOn(config.pages.customRoutes[0], 'handler').and.callThrough();
|
|
|
|
const url = `${config.publicServerURL}/apps/${config.appId}/custom_page`;
|
|
const response = await request({
|
|
url: url,
|
|
followRedirects: false,
|
|
}).catch(e => e);
|
|
expect(response.status).toBe(200);
|
|
expect(response.text).toMatch(config.appName);
|
|
expect(handlerSpy).toHaveBeenCalled();
|
|
});
|
|
|
|
it('returns 404 if custom route does not return page', async () => {
|
|
config.pages.customRoutes = [
|
|
{
|
|
method: 'GET',
|
|
path: 'custom_page',
|
|
handler: async () => {},
|
|
},
|
|
];
|
|
await reconfigureServer(config);
|
|
const handlerSpy = spyOn(config.pages.customRoutes[0], 'handler').and.callThrough();
|
|
|
|
const url = `${config.publicServerURL}/apps/${config.appId}/custom_page`;
|
|
const response = await request({
|
|
url: url,
|
|
followRedirects: false,
|
|
}).catch(e => e);
|
|
expect(response.status).toBe(404);
|
|
expect(response.text).toMatch('Not found');
|
|
expect(handlerSpy).toHaveBeenCalled();
|
|
});
|
|
});
|
|
|
|
describe('custom endpoint', () => {
|
|
it('password reset works with custom endpoint', async () => {
|
|
config.pages.pagesEndpoint = 'customEndpoint';
|
|
await reconfigureServer(config);
|
|
const sendPasswordResetEmail = spyOn(
|
|
config.emailAdapter,
|
|
'sendPasswordResetEmail'
|
|
).and.callThrough();
|
|
const user = new Parse.User();
|
|
user.setUsername('exampleUsername');
|
|
user.setPassword('examplePassword');
|
|
user.set('email', 'mail@example.com');
|
|
await user.signUp();
|
|
await Parse.User.requestPasswordReset(user.getEmail());
|
|
|
|
const link = sendPasswordResetEmail.calls.all()[0].args[0].link;
|
|
const linkResponse = await request({
|
|
url: link,
|
|
followRedirects: false,
|
|
});
|
|
expect(linkResponse.status).toBe(200);
|
|
|
|
const appId = linkResponse.headers['x-parse-page-param-appid'];
|
|
const token = linkResponse.headers['x-parse-page-param-token'];
|
|
const username = linkResponse.headers['x-parse-page-param-username'];
|
|
const publicServerUrl = linkResponse.headers['x-parse-page-param-publicserverurl'];
|
|
const passwordResetPagePath = pageResponse.calls.all()[0].args[0];
|
|
expect(appId).toBeDefined();
|
|
expect(token).toBeDefined();
|
|
expect(username).toBeDefined();
|
|
expect(publicServerUrl).toBeDefined();
|
|
expect(passwordResetPagePath).toMatch(new RegExp(`\/${pages.passwordReset.defaultFile}`));
|
|
pageResponse.calls.reset();
|
|
|
|
const formUrl = `${publicServerUrl}/${config.pages.pagesEndpoint}/${appId}/request_password_reset`;
|
|
const formResponse = await request({
|
|
url: formUrl,
|
|
method: 'POST',
|
|
body: {
|
|
token,
|
|
username,
|
|
new_password: 'newPassword',
|
|
},
|
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
followRedirects: false,
|
|
});
|
|
expect(formResponse.status).toEqual(200);
|
|
expect(pageResponse.calls.all()[0].args[0]).toContain(
|
|
`/${pages.passwordResetSuccess.defaultFile}`
|
|
);
|
|
});
|
|
|
|
it_id('81c1c28e-5dfd-4ffb-a09b-283156c08483')(it)('email verification works with custom endpoint', async () => {
|
|
config.pages.pagesEndpoint = 'customEndpoint';
|
|
await reconfigureServer(config);
|
|
const sendVerificationEmail = spyOn(
|
|
config.emailAdapter,
|
|
'sendVerificationEmail'
|
|
).and.callThrough();
|
|
const user = new Parse.User();
|
|
user.setUsername('exampleUsername');
|
|
user.setPassword('examplePassword');
|
|
user.set('email', 'mail@example.com');
|
|
await user.signUp();
|
|
await jasmine.timeout();
|
|
|
|
const link = sendVerificationEmail.calls.all()[0].args[0].link;
|
|
const linkResponse = await request({
|
|
url: link,
|
|
followRedirects: false,
|
|
});
|
|
expect(linkResponse.status).toBe(200);
|
|
const pagePath = pageResponse.calls.all()[0].args[0];
|
|
expect(pagePath).toMatch(new RegExp(`\/${pages.emailVerificationSuccess.defaultFile}`));
|
|
});
|
|
});
|
|
});
|
|
});
|