Merge pull request #1291 from LLK/release/2.2.20

[Master] Release 2.2.20
This commit is contained in:
Matthew Taylor 2017-04-26 22:20:07 -04:00 committed by GitHub
commit aef7b5e834
53 changed files with 975 additions and 211 deletions

View file

@ -67,6 +67,9 @@ functional:
integration:
$(TAP) ./test/integration/*.js
smoke:
$(TAP) ./test/integration/smoke-testing/*.js --timeout=3600
localization:
$(TAP) ./test/localization/*.js

View file

@ -1,18 +1,12 @@
var async = require('async');
var defaults = require('lodash.defaults');
var glob = require('glob');
var path = require('path');
var fastlyConfig = require('./lib/fastly-config-methods');
var route_json = require('../src/routes.json');
const FASTLY_SERVICE_ID = process.env.FASTLY_SERVICE_ID || '';
const S3_BUCKET_NAME = process.env.S3_BUCKET_NAME || '';
const PASS_REQUEST_CONDITION_NAME = 'Pass';
const NOT_PASS_REQUEST_CONDITION_NAME = '!(Pass)';
const PASS_CACHE_CONDITION_NAME = 'Cache Pass';
const BUCKET_NAME_HEADER_NAME = 'Bucket name';
var fastly = require('./lib/fastly-extended')(process.env.FASTLY_API_KEY, FASTLY_SERVICE_ID);
var extraAppRoutes = [
@ -23,87 +17,8 @@ var extraAppRoutes = [
'/[^\/]*\.html$'
];
/*
* Given the relative path to the static directory, return an array of
* patterns matching the files and directories there.
*/
var getStaticPaths = function (pathToStatic) {
var staticPaths = glob.sync(path.resolve(__dirname, pathToStatic));
return staticPaths.filter(function (pathName) {
// Exclude view html, resolve everything else in the build
return path.extname(pathName) !== '.html';
}).map(function (pathName) {
// Reduce absolute path to relative paths like '/js'
var base = path.dirname(path.resolve(__dirname, pathToStatic));
return pathName.replace(base, '') + (path.extname(pathName) ? '' : '/');
});
};
/*
* Given a list of express routes, return a list of patterns to match
* the express route and a static view file associated with the route
*/
var getViewPaths = function (routes) {
return routes.reduce(function (paths, route) {
var path = route.routeAlias || route.pattern;
if (paths.indexOf(path) === -1) {
paths.push(path);
}
return paths;
}, []);
};
/*
* Translate an express-style pattern e.g. /path/:arg/ to a regex
* all :arguments become .+?
*/
var expressPatternToRegex = function (pattern) {
return pattern.replace(/(:[^/]+)/gi, '.+?');
};
/*
* Given a list of patterns for paths, OR all of them together into one
* string suitable for a Fastly condition
*/
var pathsToCondition = function (paths) {
return 'req.url~"^(' + paths.reduce(function (conditionString, pattern) {
return conditionString + (conditionString ? '|' : '') + pattern;
}, '') + ')"';
};
/*
* Helper method to NOT a condition statement
*/
var negateConditionStatement = function (statement) {
return '!(' + statement + ')';
};
/*
* Combine static paths, routes, and any additional paths to a single
* fastly condition to match req.url
*/
var getAppRouteCondition = function (pathToStatic, routes, additionalPaths) {
var staticPaths = getStaticPaths(pathToStatic);
var viewPaths = getViewPaths(routes);
var allPaths = [].concat(staticPaths, viewPaths, additionalPaths);
return pathsToCondition(allPaths);
};
var getConditionNameForRoute = function (route, type) {
return 'routes/' + route.pattern + ' (' + type + ')';
};
var getHeaderNameForRoute = function (route) {
if (route.name) return 'rewrites/' + route.name;
if (route.redirect) return 'redirects/' + route.pattern;
};
var getResponseNameForRoute = function (route) {
return 'redirects/' + route.pattern;
};
var routes = route_json.map(function (route) {
return defaults({}, {pattern: expressPatternToRegex(route.pattern)}, route);
return defaults({}, {pattern: fastlyConfig.expressPatternToRegex(route.pattern)}, route);
});
async.auto({
@ -121,76 +36,43 @@ async.auto({
}
});
},
notPassRequestCondition: ['version', function (cb, results) {
var statement = getAppRouteCondition('../build/*', routes, extraAppRoutes);
var condition = {
name: NOT_PASS_REQUEST_CONDITION_NAME,
statement: statement,
type: 'REQUEST',
priority: 10
};
fastly.setCondition(results.version, condition, cb);
}],
setBucketNameHeader: ['version', 'notPassRequestCondition', function (cb, results) {
var header = {
name: BUCKET_NAME_HEADER_NAME,
action: 'set',
ignore_if_set: 0,
type: 'REQUEST',
dst: 'http.host',
src: '"' + S3_BUCKET_NAME + '"',
request_condition: results.notPassRequestCondition.name,
priority: 1
};
fastly.setFastlyHeader(results.version, header, cb);
}],
passRequestCondition: ['version', 'notPassRequestCondition', function (cb, results) {
var condition = {
name: PASS_REQUEST_CONDITION_NAME,
statement: negateConditionStatement(results.notPassRequestCondition.statement),
type: 'REQUEST',
priority: 10
};
fastly.setCondition(results.version, condition, cb);
}],
passRequestSettingsCondition: ['version', 'passRequestCondition', function (cb, results) {
fastly.request(
'PUT',
fastly.getFastlyAPIPrefix(FASTLY_SERVICE_ID, results.version) + '/request_settings/Pass',
{request_condition: results.passRequestCondition.name},
recvCustomVCL: ['version', function (cb, results) {
// For all the routes in routes.json, construct a varnish-style regex that matches
// on any of those route conditions.
var notPassStatement = fastlyConfig.getAppRouteCondition('../build/*', routes, extraAppRoutes, __dirname);
// For all the routes in routes.json, construct a varnish-style regex that matches
// only if NONE of those routes are matched.
var passStatement = fastlyConfig.negateConditionStatement(notPassStatement);
// For a non-pass condition, point backend at s3
var backendCondition = fastlyConfig.setBackend(
'F_s3',
S3_BUCKET_NAME,
notPassStatement
);
// For a pass condition, set forwarding headers
var forwardCondition = fastlyConfig.setForwardHeaders(passStatement);
fastly.setCustomVCL(
results.version,
'recv-condition',
backendCondition + forwardCondition,
cb
);
}],
backendCondition: ['version', 'notPassRequestCondition', function (cb, results) {
fastly.request(
'PUT',
fastly.getFastlyAPIPrefix(FASTLY_SERVICE_ID, results.version) + '/backend/s3',
{request_condition: results.notPassRequestCondition.name},
cb
);
}],
passCacheCondition: ['version', 'passRequestCondition', function (cb, results) {
var condition = {
name: PASS_CACHE_CONDITION_NAME,
type: 'CACHE',
statement: results.passRequestCondition.statement,
priority: results.passRequestCondition.priority
};
fastly.setCondition(results.version, condition, cb);
}],
passCacheSettingsCondition: ['version', 'passCacheCondition', function (cb, results) {
fastly.request(
'PUT',
fastly.getFastlyAPIPrefix(FASTLY_SERVICE_ID, results.version) + '/cache_settings/Pass',
{cache_condition: results.passCacheCondition.name},
cb
fetchCustomVCL: ['version', function (cb, results) {
var passStatement = fastlyConfig.negateConditionStatement(
fastlyConfig.getAppRouteCondition('../build/*', routes, extraAppRoutes, __dirname)
);
var ttlCondition = fastlyConfig.setResponseTTL(passStatement);
fastly.setCustomVCL(results.version, 'fetch-condition', ttlCondition, cb);
}],
appRouteRequestConditions: ['version', function (cb, results) {
var conditions = {};
async.forEachOf(routes, function (route, id, cb2) {
var condition = {
name: getConditionNameForRoute(route, 'request'),
name: fastlyConfig.getConditionNameForRoute(route, 'request'),
statement: 'req.url ~ "' + route.pattern + '"',
type: 'REQUEST',
// Priority needs to be > 1 to not interact with http->https redirect
@ -213,7 +95,7 @@ async.auto({
async.auto({
responseCondition: function (cb3) {
var condition = {
name: getConditionNameForRoute(route, 'response'),
name: fastlyConfig.getConditionNameForRoute(route, 'response'),
statement: 'req.url ~ "' + route.pattern + '"',
type: 'RESPONSE',
priority: id
@ -222,16 +104,16 @@ async.auto({
},
responseObject: function (cb3) {
var responseObject = {
name: getResponseNameForRoute(route),
name: fastlyConfig.getResponseNameForRoute(route),
status: 301,
response: 'Moved Permanently',
request_condition: getConditionNameForRoute(route, 'request')
request_condition: fastlyConfig.getConditionNameForRoute(route, 'request')
};
fastly.setResponseObject(results.version, responseObject, cb3);
},
redirectHeader: ['responseCondition', function (cb3, redirectResults) {
var header = {
name: getHeaderNameForRoute(route),
name: fastlyConfig.getHeaderNameForRoute(route),
action: 'set',
ignore_if_set: 0,
type: 'RESPONSE',
@ -248,7 +130,7 @@ async.auto({
});
} else {
var header = {
name: getHeaderNameForRoute(route, 'request'),
name: fastlyConfig.getHeaderNameForRoute(route, 'request'),
action: 'set',
ignore_if_set: 0,
type: 'REQUEST',

View file

@ -0,0 +1,139 @@
var glob = require('glob');
var path = require('path');
var FastlyConfigMethods = {
/*
* Given the relative path to the static directory, return an array of
* patterns matching the files and directories there.
*/
getStaticPaths: function (rootDir, pathToStatic) {
var staticPaths = glob.sync(path.resolve(rootDir, pathToStatic));
return staticPaths.filter(function (pathName) {
// Exclude view html, resolve everything else in the build
return path.extname(pathName) !== '.html';
}).map(function (pathName) {
// Reduce absolute path to relative paths like '/js'
var base = path.dirname(path.resolve(rootDir, pathToStatic));
return pathName.replace(base, '') + (path.extname(pathName) ? '' : '/');
});
},
/*
* Given a list of express routes, return a list of patterns to match
* the express route and a static view file associated with the route
*/
getViewPaths: function (routes) {
return routes.reduce(function (paths, route) {
var path = route.routeAlias || route.pattern;
if (paths.indexOf(path) === -1) {
paths.push(path);
}
return paths;
}, []);
},
/*
* Translate an express-style pattern e.g. /path/:arg/ to a regex
* all :arguments become .+?
*/
expressPatternToRegex: function (pattern) {
return pattern.replace(/(:[^/]+)/gi, '.+?');
},
/*
* Given a list of patterns for paths, OR all of them together into one
* string suitable for a Fastly condition
*/
pathsToCondition: function (paths) {
return 'req.url~"^(' + paths.reduce(function (conditionString, pattern) {
return conditionString + (conditionString ? '|' : '') + pattern;
}, '') + ')"';
},
/*
* Helper method to NOT a condition statement
*/
negateConditionStatement: function (statement) {
return '!(' + statement + ')';
},
/*
* Combine static paths, routes, and any additional paths to a single
* fastly condition to match req.url
*/
getAppRouteCondition: function (pathToStatic, routes, additionalPaths, rootDir) {
var staticPaths = FastlyConfigMethods.getStaticPaths(rootDir, pathToStatic);
var viewPaths = FastlyConfigMethods.getViewPaths(routes);
var allPaths = [].concat(staticPaths, viewPaths, additionalPaths);
return FastlyConfigMethods.pathsToCondition(allPaths);
},
getConditionNameForRoute: function (route, type) {
return 'routes/' + route.pattern + ' (' + type + ')';
},
getHeaderNameForRoute: function (route) {
if (route.name) return 'rewrites/' + route.name;
if (route.redirect) return 'redirects/' + route.pattern;
},
getResponseNameForRoute: function (route) {
return 'redirects/' + route.pattern;
},
/**
* Returns custom vcl configuration as a string for setting the backend
* of a request to the given backend/host.
*
* @param {string} backend name of the backend declared in fastly
* @param {string} host name of the s3 bucket to be set as the host
* @param {string} condition condition under which backend should be set
*/
setBackend: function (backend, host, condition) {
return '' +
'if (' + condition + ') {\n' +
' set req.backend = ' + backend + ';\n' +
' set req.http.host = \"' + host + '\";\n' +
'}\n';
},
/**
* Returns custom vcl configuration as a string for headers that
* should be added for the condition in which a request is forwarded.
*
* @param {string} condition condition under which to set pass headers
*/
setForwardHeaders: function (condition) {
return '' +
'if (' + condition + ') {\n' +
' if (!req.http.Fastly-FF) {\n' +
' if (req.http.X-Forwarded-For) {\n' +
' set req.http.Fastly-Temp-XFF = req.http.X-Forwarded-For \", \" client.ip;\n' +
' } else {\n' +
' set req.http.Fastly-Temp-XFF = client.ip;\n' +
' }\n' +
' } else {\n' +
' set req.http.Fastly-Temp-XFF = req.http.X-Forwarded-For;\n' +
' }\n' +
' set req.grace = 60s;\n' +
' return(pass);\n' +
'}\n';
},
/**
* Returns custom vcl configuration as a string that sets the varnish
* Time to Live (TTL) for responses that come from s3.
*
* @param {string} condition condition under which the response should be set
*/
setResponseTTL: function (condition) {
return '' +
'if (' + condition + ') {\n' +
' set beresp.ttl = 0s;\n' +
' set beresp.grace = 0s;\n' +
' return(pass);\n' +
'}\n';
}
};
module.exports = FastlyConfigMethods;

View file

@ -166,5 +166,40 @@ module.exports = function (apiKey, serviceId) {
this.request('PUT', url, cb);
};
/**
* Upsert a custom vcl file. Attempts a PUT, and falls back
* to POST if not there already.
*
* @param {number} version current version number for fastly service
* @param {string} name name of the custom vcl file to be upserted
* @param {string} vcl stringified custom vcl to be uploaded
* @param {Function} cb function that takes in two args: err, response
*/
fastly.setCustomVCL = function (version, name, vcl, cb) {
if (!this.serviceId) {
return cb('Failed to set response object. No serviceId configured');
}
var url = this.getFastlyAPIPrefix(this.serviceId, version) + '/vcl/' + name;
var postUrl = this.getFastlyAPIPrefix(this.serviceId, version) + '/vcl';
var content = {content: vcl};
return this.request('PUT', url, content, function (err, response) {
if (err && err.statusCode === 404) {
content.name = name;
this.request('POST', postUrl, content, function (err, response) {
if (err) {
return cb('Failed while adding custom vcl \"' + name + '\": ' + err);
}
return cb(null, response);
});
return;
}
if (err) {
return cb('Failed to update custom vcl \"' + name + '\": ' + err);
}
return cb(null, response);
}.bind(this));
};
return fastly;
};

View file

@ -1,23 +1,67 @@
{
"en": {
"cards.starterLink": "/pdfs/cards/Scratch2Cards.pdf",
"cards.nameLink": "/pdfs/cards/AnimateYourNameCards.pdf",
"cards.flyLink": "/pdfs/cards/FlyCards.pdf",
"cards.raceLink": "/pdfs/cards/RaceGameCards.pdf",
"cards.musicLink": "/pdfs/cards/MusicCards.pdf",
"cards.hideLink": "/pdfs/cards/Hide-and-Seek-Cards.pdf",
"cards.storyLink": "/pdfs/cards/StoryCards.pdf",
"cards.dressupLink": "/pdfs/cards/DressupCards.pdf",
"cards.pongLink": "/pdfs/cards/PongCards.pdf",
"cards.danceLink": "/pdfs/cards/DanceCards.pdf",
"cards.catchLink": "/pdfs/cards/CatchCards.pdf",
"cards.petLink": "/pdfs/cards/PetCards.pdf"
"cards.starterLink": "/pdfs/cards/Scratch2Cards.pdf",
"cards.nameLink": "/pdfs/cards/AnimateYourNameCards.pdf",
"cards.flyLink": "/pdfs/cards/FlyCards.pdf",
"cards.raceLink": "/pdfs/cards/RaceGameCards.pdf",
"cards.musicLink": "/pdfs/cards/MusicCards.pdf",
"cards.hideLink": "/pdfs/cards/Hide-and-Seek-Cards.pdf",
"cards.storyLink": "/pdfs/cards/StoryCards.pdf",
"cards.dressupLink": "/pdfs/cards/DressupCards.pdf",
"cards.pongLink": "/pdfs/cards/PongCards.pdf",
"cards.danceLink": "/pdfs/cards/DanceCards.pdf",
"cards.catchLink": "/pdfs/cards/CatchCards.pdf",
"cards.petLink": "/pdfs/cards/PetCards.pdf",
"ttt.MakeItFlyActivityLoc": "/pdfs/cards/FlyCards.pdf",
"ttt.MakeItFlyGuideLoc": "/pdfs/guides/FlyGuide.pdf",
"ttt.AnimateYourNameActivityLoc": "/pdfs/cards/AnimateYourNameCards.pdf",
"ttt.AnimateYourNameGuideLoc": "/pdfs/guides/NameGuide.pdf",
"ttt.MakeMusicActivityLoc": "/pdfs/cards/MusicCards.pdf",
"ttt.MakeMusicGuideLoc": "/pdfs/guides/MusicGuide.pdf",
"ttt.RaceActivityLoc": "/pdfs/cards/RaceGameCards.pdf",
"ttt.RaceGuideLoc": "/pdfs/guides/RaceGuide.pdf",
"ttt.DanceActivityLoc": "/pdfs/cards/DanceCards.pdf",
"ttt.DanceGuideLoc": "/pdfs/guides/DanceGuide.pdf",
"ttt.PongActivityLoc": "/pdfs/cards/PongCards.pdf",
"ttt.PongGuideLoc": "/pdfs/guides/PongGuide.pdf",
"ttt.CatchActivityLoc": "/pdfs/cards/CatchCards.pdf",
"ttt.CatchGuideLoc": "/pdfs/guides/CatchGuide.pdf",
"ttt.HideAndSeekActivityLoc": "/pdfs/cards/Hide-and-Seek-Cards.pdf",
"ttt.HideAndSeekGuideLoc": "/pdfs/guides/Hide-and-Seek-Guide.pdf",
"ttt.VirtualPetActivityLoc": "/pdfs/cards/PetCards.pdf",
"ttt.VirtualPetGuideLoc": "/pdfs/guides/PetGuide.pdf",
"ttt.DressupActivityLoc": "/pdfs/cards/DressupCards.pdf",
"ttt.DressupGuideLoc": "/pdfs/guides/DressupGuide.pdf",
"ttt.StoryActivityLoc": "/pdfs/cards/StoryCards.pdf",
"ttt.StoryGuideLoc": "/pdfs/guides/StoryGuide.pdf"
},
"ar": {
"cards.starterLink": "//cdn.scratch.mit.edu/scratchr2/static/pdfs/help/ar/Scratch2Cards.pdf"
},
"ca": {
"cards.starterLink": "//cdn.scratch.mit.edu/scratchr2/static/pdfs/help/ca/Scratch2Cards.pdf"
"cards.nameLink": "/pdfs/cards/ca/AnimateYourNameCards.pdf",
"cards.catchLink": "/pdfs/cards/ca/CatchCards.pdf",
"cards.danceLink": "/pdfs/cards/ca/DanceCards.pdf",
"cards.dressupLink": "/pdfs/cards/ca/DressupCards.pdf",
"cards.flyLink": "/pdfs/cards/ca/FlyCards.pdf",
"cards.hideLink": "/pdfs/cards/ca/Hide-and-Seek-Cards.pdf",
"cards.musicLink": "/pdfs/cards/ca/MusicCards.pdf",
"cards.petLink": "/pdfs/cards/ca/PetCards.pdf",
"cards.pongLink": "/pdfs/cards/ca/PongCards.pdf",
"cards.raceLink": "/pdfs/cards/ca/RaceGameCards.pdf",
"cards.starterLink": "/pdfs/cards/ca/Scratch2Cards.pdf",
"cards.storyLink": "/pdfs/cards/ca/StoryCards.pdf",
"ttt.MakeItFlyActivityLoc": "/pdfs/cards/ca/FlyCards.pdf",
"ttt.AnimateYourNameActivityLoc": "/pdfs/cards/ca/AnimateYourNameCards.pdf",
"ttt.MakeMusicActivityLoc": "/pdfs/cards/ca/MusicCards.pdf",
"ttt.RaceActivityLoc": "/pdfs/cards/ca/RaceGameCards.pdf",
"ttt.DanceActivityLoc": "/pdfs/cards/ca/DanceCards.pdf",
"ttt.PongActivityLoc": "/pdfs/cards/ca/PongCards.pdf",
"ttt.CatchActivityLoc": "/pdfs/cards/ca/CatchCards.pdf",
"ttt.HideAndSeekActivityLoc": "/pdfs/cards/ca/Hide-and-Seek-Cards.pdf",
"ttt.VirtualPetActivityLoc": "/pdfs/cards/ca/PetCards.pdf",
"ttt.DressupActivityLoc": "/pdfs/cards/ca/DressupCards.pdf",
"ttt.StoryActivityLoc": "/pdfs/cards/ca/StoryCards.pdf"
},
"cs": {
"cards.starterLink": "//cdn.scratch.mit.edu/scratchr2/static/pdfs/help/cs/Scratch2Cards.pdf"
@ -26,7 +70,29 @@
"cards.starterLink": "//cdn.scratch.mit.edu/scratchr2/static/pdfs/help/de/Scratch2Cards.pdf"
},
"es": {
"cards.starterLink": "//cdn.scratch.mit.edu/scratchr2/static/pdfs/help/es/Scratch2Cards.pdf"
"cards.nameLink": "/pdfs/cards/es/AnimateYourNameCards.pdf",
"cards.catchLink": "/pdfs/cards/es/CatchCards.pdf",
"cards.danceLink": "/pdfs/cards/es/DanceCards.pdf",
"cards.dressupLink": "/pdfs/cards/es/DressupCards.pdf",
"cards.flyLink": "/pdfs/cards/es/FlyCards.pdf",
"cards.hideLink": "/pdfs/cards/es/Hide-and-Seek-Cards.pdf",
"cards.musicLink": "/pdfs/cards/es/MusicCards.pdf",
"cards.petLink": "/pdfs/cards/es/PetCards.pdf",
"cards.pongLink": "/pdfs/cards/es/PongCards.pdf",
"cards.raceLink": "/pdfs/cards/es/RaceGameCards.pdf",
"cards.starterLink": "/pdfs/cards/es/Scratch2Cards.pdf",
"cards.storyLink": "/pdfs/cards/es/StoryCards.pdf",
"ttt.MakeItFlyActivityLoc": "/pdfs/cards/es/FlyCards.pdf",
"ttt.AnimateYourNameActivityLoc": "/pdfs/cards/es/AnimateYourNameCards.pdf",
"ttt.MakeMusicActivityLoc": "/pdfs/cards/es/MusicCards.pdf",
"ttt.RaceActivityLoc": "/pdfs/cards/es/RaceGameCards.pdf",
"ttt.DanceActivityLoc": "/pdfs/cards/es/DanceCards.pdf",
"ttt.PongActivityLoc": "/pdfs/cards/es/PongCards.pdf",
"ttt.CatchActivityLoc": "/pdfs/cards/es/CatchCards.pdf",
"ttt.HideAndSeekActivityLoc": "/pdfs/cards/es/Hide-and-Seek-Cards.pdf",
"ttt.VirtualPetActivityLoc": "/pdfs/cards/es/PetCards.pdf",
"ttt.DressupActivityLoc": "/pdfs/cards/es/DressupCards.pdf",
"ttt.StoryActivityLoc": "/pdfs/cards/es/StoryCards.pdf"
},
"fr": {
"cards.starterLink": "//cdn.scratch.mit.edu/scratchr2/static/pdfs/help/fr/Scratch2Cards.pdf"
@ -54,5 +120,32 @@
},
"sl": {
"cards.starterLink": "//cdn.scratch.mit.edu/scratchr2/static/pdfs/help/sl/Scratch2Cards.pdf"
},
"sv": {
"cards.starterLink": "/pdfs/cards/sv/Scratch2Cards.pdf"
},
"zh-tw": {
"cards.nameLink": "/pdfs/cards/zh-tw/AnimateYourNameCards.pdf",
"cards.catchLink": "/pdfs/cards/zh-tw/CatchCards.pdf",
"cards.danceLink": "/pdfs/cards/zh-tw/DanceCards.pdf",
"cards.dressupLink": "/pdfs/cards/zh-tw/DressupCards.pdf",
"cards.flyLink": "/pdfs/cards/zh-tw/FlyCards.pdf",
"cards.hideLink": "/pdfs/cards/zh-tw/Hide-and-Seek-Cards.pdf",
"cards.musicLink": "/pdfs/cards/zh-tw/MusicCards.pdf",
"cards.petLink": "/pdfs/cards/zh-tw/PetCards.pdf",
"cards.pongLink": "/pdfs/cards/zh-tw/PongCards.pdf",
"cards.raceLink": "/pdfs/cards/zh-tw/RaceGameCards.pdf",
"cards.storyLink": "/pdfs/cards/zh-tw/StoryCards.pdf",
"ttt.MakeItFlyActivityLoc": "/pdfs/cards/zh-tw/FlyCards.pdf",
"ttt.AnimateYourNameActivityLoc": "/pdfs/cards/zh-tw/AnimateYourNameCards.pdf",
"ttt.MakeMusicActivityLoc": "/pdfs/cards/zh-tw/MusicCards.pdf",
"ttt.RaceActivityLoc": "/pdfs/cards/zh-tw/RaceGameCards.pdf",
"ttt.DanceActivityLoc": "/pdfs/cards/zh-tw/DanceCards.pdf",
"ttt.PongActivityLoc": "/pdfs/cards/zh-tw/PongCards.pdf",
"ttt.CatchActivityLoc": "/pdfs/cards/zh-tw/CatchCards.pdf",
"ttt.HideAndSeekActivityLoc": "/pdfs/cards/zh-tw/Hide-and-Seek-Cards.pdf",
"ttt.VirtualPetActivityLoc": "/pdfs/cards/zh-tw/PetCards.pdf",
"ttt.DressupActivityLoc": "/pdfs/cards/zh-tw/DressupCards.pdf",
"ttt.StoryActivityLoc": "/pdfs/cards/zh-tw/StoryCards.pdf"
}
}
}

View file

@ -6,6 +6,7 @@
"start": "make start",
"stop": "make stop",
"test": "make test",
"smoke": "make smoke",
"watch": "make watch",
"build": "make build",
"dev": "make watch && make start &"

View file

@ -8,7 +8,6 @@
border-radius: 0 0 5px 5px;
background-color: $ui-blue;
padding: 10px;
min-width: 160px;
max-width: 260px;
overflow: visible;
color: $type-white;

View file

@ -306,7 +306,8 @@ module.exports = {
getDefaultProps: function () {
return {
waiting: false,
description: null
description: null,
birthOffset: 0
};
},
getInitialState: function () {
@ -324,13 +325,26 @@ module.exports = {
},
getYearOptions: function () {
return Array.apply(null, Array(100)).map(function (v, id) {
var year = new Date().getFullYear() - id;
var year = new Date().getFullYear() - (id + this.props.birthOffset);
return {value: year, label: year};
});
}.bind(this));
},
onChooseGender: function (name, gender) {
this.setState({otherDisabled: gender !== 'other'});
},
onValidSubmit: function (formData, reset, invalidate) {
var birthdate = new Date(
formData.user.birth.year,
formData.user.birth.month - 1,
1
);
if (((Date.now() - birthdate) / (24*3600*1000*365.25)) < this.props.birthOffset) {
return invalidate({
'user.birth.year': this.props.intl.formatMessage({id: 'teacherRegistration.validationAge'})
});
}
return this.props.onNextStep(formData);
},
render: function () {
var formatMessage = this.props.intl.formatMessage;
return (
@ -348,7 +362,7 @@ module.exports = {
tipContent={formatMessage({id: 'registration.nameStepTooltip'})} />
</p>
<Card>
<Form onValidSubmit={this.props.onNextStep}>
<Form onValidSubmit={this.onValidSubmit}>
<Select label={formatMessage({id: 'general.birthMonth'})}
name="user.birth.month"
options={this.getMonthOptions()}

View file

@ -176,14 +176,14 @@
{
"name": "jobs",
"pattern": "^/jobs/?$",
"routeAlias": "/jobs/?$",
"routeAlias": "/jobs(/moderator)?/?$",
"view": "jobs/jobs",
"title": "Jobs"
},
{
"name": "jobs-moderator",
"pattern": "^/jobs/moderator/?$",
"routeAlias": "/jobs/?$",
"routeAlias": "/jobs(/moderator)?/?$",
"view": "jobs/moderator/moderator",
"title": "Community Moderator"
},

View file

@ -12,7 +12,7 @@ var TitleBanner = require('../../components/title-banner/title-banner.jsx');
require('./developers.scss');
var Developers = React.createClass({
type: 'About',
type: 'Developers',
render: function () {
return (
<div className="developers">

View file

@ -36,5 +36,6 @@
"teacherRegistration.howUseScratch": "How do you plan to use Scratch at your organization?",
"teacherRegistration.emailStepTitle": "Email Address",
"teacherRegistration.emailStepDescription": "We will send you a confirmation email that will allow you to access your Scratch Teacher Account.",
"teacherRegistration.validationEmailMatch": "The emails do not match"
"teacherRegistration.validationEmailMatch": "The emails do not match",
"teacherRegistration.validationAge": "Sorry, teachers must be at least 13 years old"
}

View file

@ -95,7 +95,8 @@ var TeacherRegistration = intl.injectIntl(React.createClass({
<Steps.UsernameStep onNextStep={this.advanceStep}
waiting={this.state.waiting} />
<Steps.DemographicsStep onNextStep={this.advanceStep}
waiting={this.state.waiting} />
waiting={this.state.waiting}
birthOffset={13} />
<Steps.NameStep onNextStep={this.advanceStep}
waiting={this.state.waiting} />
<Steps.PhoneNumberStep onNextStep={this.advanceStep}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
static/pdfs/cards/ca/FlyCards.pdf Executable file

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
static/pdfs/cards/ca/PetCards.pdf Executable file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
static/pdfs/cards/es/PetCards.pdf Executable file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -1,33 +0,0 @@
/*
* Checks that the links in the navbar on the homepage have the right URLs to redirect to
*
* Test cases: https://github.com/LLK/scratch-www/wiki/Most-Important-Workflows#Create_should_take_you_to_the_editor
*/
var tap=require('tap');
var seleniumWebdriver = require('selenium-webdriver');
//chrome driver
var driver = new seleniumWebdriver.Builder().withCapabilities(seleniumWebdriver.Capabilities.chrome()).build();
//open scratch.ly in a new instance of the browser
driver.get('https://scratch.ly');
//find the create link within the navbar
//the create link depends on whether the user is signed in or not (tips window opens)
tap.test('checkCreateLinkWhenSignedOut', function (t) {
var xPathLink = '//li[contains(@class, "link") and contains(@class, "create")]/a';
var createLinkSignedOut = driver.findElement(seleniumWebdriver.By.xpath(xPathLink));
createLinkSignedOut.getAttribute('href').then( function (url) {
//expected value of the href
var expectedHref = '/projects/editor/?tip_bar=home';
//the create href should match `/projects/editor/?tip_bar=home`
//the create href should be at the end of the URL
t.equal(url.substr(-expectedHref.length), expectedHref);
t.end();
});
});
// close the instance of the browser
driver.quit();

View file

@ -0,0 +1,301 @@
/*
* Checks that the links in the footer on the homepage have the right URLs to redirect to
*
* Test cases: https://github.com/LLK/scratch-www/wiki/Most-Important-Workflows
*/
require('chromedriver');
var tap = require('tap');
var seleniumWebdriver = require('selenium-webdriver');
// Selenium's promise driver will be deprecated, so we should not rely on it
seleniumWebdriver.SELENIUM_PROMISE_MANAGER=0;
//chrome driver
var driver = new seleniumWebdriver.Builder().withCapabilities(seleniumWebdriver.Capabilities.chrome()).build();
var rootUrl = process.env.ROOT_URL || 'https://scratch.ly';
//timeout for each test; timeout for suite set at command line level
var options = { timeout: 20000 };
//number of tests in the plan
tap.plan(24);
tap.tearDown(function () {
//quit the instance of the browser
driver.quit();
});
tap.beforeEach(function () {
//load the page with the driver
return driver.get(rootUrl);
});
// Function clicks the link and returns the url of the resulting page
function clickFooterLinks ( linkText ) {
// Not sure if I need this first wait - maybe it solved intermittent initial failure problem?
return driver.wait(seleniumWebdriver.until.elementLocated(seleniumWebdriver.By.id('view')))
.then( function () {
return driver.wait(seleniumWebdriver.until.elementLocated(seleniumWebdriver.By
.id('footer')))
.then( function () {
return driver.findElement(seleniumWebdriver.By.linkText(linkText)); })
.then( function (element) {
return element.click(); })
.then(function () {
return driver.getCurrentUrl();
});
});
}
// ==== ABOUT SCRATCH column ====
// ABOUT SCRATCH
tap.test('clickAboutScratchLink', options, function (t) {
var linkText = 'About Scratch';
var expectedHref = '/about';
clickFooterLinks(linkText).then( function (url) {
//the href should be at the end of the URL
t.equal(url.substr(-expectedHref.length), expectedHref);
t.end();
});
});
// FOR PARENTS
tap.test('clickForParentsLink', options, function (t) {
var linkText = 'For Parents';
var expectedHref = '/parents/';
clickFooterLinks(linkText).then( function (url) {
t.equal(url.substr(-expectedHref.length), expectedHref);
t.end();
});
});
// FOR EDUCATORS
tap.test('clickForEducatorsLink', options, function (t) {
var linkText = 'For Educators';
var expectedHref = '/educators';
clickFooterLinks(linkText).then( function (url) {
t.equal(url.substr(-expectedHref.length), expectedHref);
t.end();
});
});
// FOR DEVELOPERS
tap.test('clickForDevelopersScratchLink', options, function (t) {
var linkText = 'For Developers';
var expectedHref = '/developers';
clickFooterLinks(linkText).then( function (url) {
t.equal(url.substr(-expectedHref.length), expectedHref);
t.end();
});
});
// CREDITS
tap.test('clickCreditsLink', options, function (t) {
var linkText = 'Credits';
var expectedHref = '/info/credits';
clickFooterLinks(linkText).then( function (url) {
t.equal(url.substr(-expectedHref.length), expectedHref);
t.end();
});
});
// JOBS
tap.test('clickJobsLink', options, function (t) {
var linkText = 'Jobs';
var expectedHref = '/jobs';
clickFooterLinks(linkText).then( function (url) {
t.equal(url.substr(-expectedHref.length), expectedHref);
t.end();
});
});
// PRESS
tap.test('clickPressLink', options, function (t) {
var linkText = 'Press';
var expectedHref = 'https://wiki.scratch.mit.edu/wiki/Scratch_Press';
clickFooterLinks(linkText).then( function (url) {
t.equal(url, expectedHref);
t.end();
});
});
// ==== COMMUNITY column ====
// COMMUNITY GUIDELINES
tap.test('clickCommunityGuidelinesLink', options, function (t) {
var linkText = 'Community Guidelines';
var expectedHref = '/community_guidelines';
clickFooterLinks(linkText).then( function (url) {
t.equal(url.substr(-expectedHref.length), expectedHref);
t.end();
});
});
// DISCUSSION FORUMS
tap.test('clickDiscussionForumsLink', options, function (t) {
var linkText = 'Discussion Forums';
var expectedHref = '/discuss/';
clickFooterLinks(linkText).then( function (url) {
t.equal(url.substr(-expectedHref.length), expectedHref);
t.end();
});
});
// SCRATCH WIKI
tap.test('clickScratchWikiLink', options, function (t) {
var linkText = 'Scratch Wiki';
var expectedHref = 'https://wiki.scratch.mit.edu/wiki/Scratch_Wiki_Home';
clickFooterLinks(linkText).then( function (url) {
t.equal(url, expectedHref);
t.end();
});
});
// STATISTICS
tap.test('clickStatisticsLink', options, function (t) {
var linkText = 'Statistics';
var expectedHref = '/statistics/';
clickFooterLinks(linkText).then( function (url) {
t.equal(url.substr(-expectedHref.length), expectedHref);
t.end();
});
});
// ==== SUPPORT column ====
// HELP PAGE
tap.test('clickHelpPageLink', options, function (t) {
var linkText = 'Help Page';
var expectedHref = '/help/';
clickFooterLinks(linkText).then( function (url) {
t.equal(url.substr(-expectedHref.length), expectedHref);
t.end();
});
});
// FAQ
tap.test('clickFAQLink', options, function (t) {
var linkText = 'FAQ';
var expectedHref = '/info/faq';
clickFooterLinks(linkText).then( function (url) {
t.equal(url.substr(-expectedHref.length), expectedHref);
t.end();
});
});
// OFFLINE EDITOR
tap.test('clickOfflineEditorLink', options, function (t) {
var linkText = 'Offline Editor';
var expectedHref = '/scratch2download/';
clickFooterLinks(linkText).then( function (url) {
t.equal(url.substr(-expectedHref.length), expectedHref);
t.end();
});
});
// CONTACT US
tap.test('clickContactUsLink', options, function (t) {
var linkText = 'Contact Us';
var expectedHref = '/contact-us/';
clickFooterLinks(linkText).then( function (url) {
t.equal(url.substr(-expectedHref.length), expectedHref);
t.end();
});
});
// DONATE
tap.test('clickDonateLink', options, function (t) {
var linkText = 'Donate';
var expectedHref = 'https://secure.donationpay.org/scratchfoundation/';
clickFooterLinks(linkText).then( function (url) {
t.equal(url.substr(-expectedHref.length), expectedHref);
t.end();
});
});
// ==== LEGAL column ====
// TERMS OF USE
tap.test('clickTermsOfUseLink', options, function (t) {
var linkText = 'Terms of Use';
var expectedHref = '/terms_of_use';
clickFooterLinks(linkText).then( function (url) {
t.equal(url.substr(-expectedHref.length), expectedHref);
t.end();
});
});
// PRIVACY POLICY
tap.test('clickPrivacyPolicyLink', options, function (t) {
var linkText = 'Privacy Policy';
var expectedHref = '/privacy_policy';
clickFooterLinks(linkText).then( function (url) {
t.equal(url.substr(-expectedHref.length), expectedHref);
t.end();
});
});
// DMCA
tap.test('clickDMCALink', options, function (t) {
var linkText = 'DMCA';
var expectedHref = '/DMCA';
clickFooterLinks(linkText).then( function (url) {
t.equal(url.substr(-expectedHref.length), expectedHref);
t.end();
});
});
// ==== SCRATCH FAMILY column ====
// SCRATCH ED (SCRATCHED)
tap.test('clickScratchEdLink', options, function (t) {
var linkText = 'ScratchEd';
var expectedHref = 'http://scratched.gse.harvard.edu/';
clickFooterLinks(linkText).then( function (url) {
t.equal(url, expectedHref);
t.end();
});
});
// SCRATCH JR (SCRATCHJR)
tap.test('clickScratchJrLink', options, function (t) {
var linkText = 'ScratchJr';
var expectedHref = 'http://www.scratchjr.org/';
clickFooterLinks(linkText).then( function (url) {
t.equal(url, expectedHref);
t.end();
});
});
// SCRATCH DAY
tap.test('clickScratchDayLink', options, function (t) {
var linkText = 'Scratch Day';
var expectedHref = 'https://day.scratch.mit.edu/';
clickFooterLinks(linkText).then( function (url) {
t.equal(url, expectedHref);
t.end();
});
});
// SCRATCH CONFERENCE
tap.test('clickScratchConferenceLink', options, function (t) {
var linkText = 'Scratch Conference';
var expectedHref = '/conference';
clickFooterLinks(linkText).then( function (url) {
t.equal(url.substr(-expectedHref.length), expectedHref);
t.end();
});
});
// SCRATCH FOUNDATION
tap.test('clickScratchFoundationLink', options, function (t) {
var linkText = 'Scratch Foundation';
var expectedHref = 'https://www.scratchfoundation.org/';
clickFooterLinks(linkText).then( function (url) {
t.equal(url, expectedHref);
t.end();
});
});

View file

@ -0,0 +1,131 @@
/*
* Checks that the links in the navbar on the homepage have the right URLs to redirect to
*
* Test cases: https://github.com/LLK/scratch-www/wiki/Most-Important-Workflows
*/
require('chromedriver');
var seleniumWebdriver = require('selenium-webdriver');
var tap = require('tap');
// Selenium's promise driver will be deprecated, so we should not rely on it
seleniumWebdriver.SELENIUM_PROMISE_MANAGER=0;
//Set test url through environment variable
var rootUrl = process.env.ROOT_URL || 'https://scratch.ly';
//chrome driver
var driver = new seleniumWebdriver.Builder().withCapabilities(seleniumWebdriver.Capabilities.chrome()).build();
//number of tests in the plan
tap.plan(8);
tap.tearDown(function () {
//quit the instance of the browser
driver.quit();
});
tap.beforeEach(function () {
//load the page with the driver
return driver.get(rootUrl);
});
// ==== Links in navbar ====
//the create link changes depending on whether the user is signed in or not (tips window opens)
tap.test('checkCreateLinkWhenSignedOut', function (t) {
var xPathLink = '//li[contains(@class, "link") and contains(@class, "create")]/a';
var expectedHref = '/projects/editor/?tip_bar=home';
driver.findElement(seleniumWebdriver.By.xpath(xPathLink))
.then( function (element) {
return element.getAttribute('href');})
.then( function (url) {
t.equal(url.substr(-expectedHref.length), expectedHref);
t.end();
});
});
tap.test('checkExploreLinkWhenSignedOut', function (t) {
var xPathLink = '//li[contains(@class, "link") and contains(@class, "explore")]/a';
var expectedHref = '/explore/projects/all';
driver.findElement(seleniumWebdriver.By.xpath(xPathLink))
.then( function (element) {
return element.getAttribute('href');})
.then( function (url) {
t.equal(url.substr(-expectedHref.length), expectedHref);
t.end();
});
});
tap.test('checkDiscussLinkWhenSignedOut', function (t) {
var xPathLink = '//li[contains(@class, "link") and contains(@class, "discuss")]/a';
var expectedHref = '/discuss';
driver.findElement(seleniumWebdriver.By.xpath(xPathLink))
.then( function (element) {
return element.getAttribute('href');})
.then( function (url) {
t.equal(url.substr(-expectedHref.length), expectedHref);
t.end();
});
});
tap.test('checkAboutLinkWhenSignedOut', function (t) {
var xPathLink = '//li[contains(@class, "link") and contains(@class, "about")]/a';
var expectedHref = '/about';
driver.findElement(seleniumWebdriver.By.xpath(xPathLink))
.then( function (element) {
return element.getAttribute('href');})
.then( function (url) {
t.equal(url.substr(-expectedHref.length), expectedHref);
t.end();
});
});
tap.test('checkHelpLinkWhenSignedOut', function (t) {
var xPathLink = '//li[contains(@class, "link") and contains(@class, "help")]/a';
var expectedHref = '/help';
driver.findElement(seleniumWebdriver.By.xpath(xPathLink))
.then( function (element) {
return element.getAttribute('href');})
.then( function (url) {
t.equal(url.substr(-expectedHref.length), expectedHref);
t.end();
});
});
// ==== Search bar ====
tap.test('checkSearchBar', function (t) {
var xPathLink = '//input[@id="frc-q-1088"]';
// search bar should exist
driver.findElement(seleniumWebdriver.By.xpath(xPathLink)).then( function (element) {
t.ok(element);
t.end();
});
});
// ==== Join Scratch & Sign In ====
tap.test('checkJoinScratchLinkWhenSignedOut', function (t) {
var xPathLink = '//li[contains(@class, "link") and contains(@class, "right") and contains(@class, "join")]/a';
var expectedText = 'Join Scratch';
driver.findElement(seleniumWebdriver.By.xpath(xPathLink))
.then( function (element) {
return element.getText('a');})
.then( function (text) {
t.equal(text, expectedText);
t.end();
});
});
tap.test('checkSignInLinkWhenSignedOut', function (t) {
var xPathLink = '//li[contains(@class, "link") and contains(@class, "right") and contains(@class, "login-item")]/a';
var expectedText = 'Sign in';
driver.findElement(seleniumWebdriver.By.xpath(xPathLink))
.then( function (element) {
return element.getText('a');})
.then( function (text) {
t.equal(text, expectedText);
t.end();
});
});

View file

@ -0,0 +1,94 @@
/*
* Checks that the some of the homepage rows on the homepage are displayed and
* contents have the right URLs to redirect to
*
* Test cases: https://github.com/LLK/scratch-www/wiki/Most-Important-Workflows
*/
require('chromedriver');
var tap = require('tap');
var seleniumWebdriver = require('selenium-webdriver');
// Selenium's promise driver will be deprecated, so we should not rely on it
seleniumWebdriver.SELENIUM_PROMISE_MANAGER=0;
//chrome driver
var driver = new seleniumWebdriver.Builder().withCapabilities(seleniumWebdriver.Capabilities.chrome()).build();
var rootUrl = process.env.ROOT_URL || 'https://scratch.ly';
//number of tests in the plan
tap.plan(4);
tap.tearDown(function () {
//quit the instance of the browser
driver.quit();
});
tap.beforeEach(function () {
//load the page with the driver
return driver.get(rootUrl);
});
//checks that the title of the first row is Featured Projects
tap.test('checkFeaturedProjectsRowTitleWhenSignedOut', function (t) {
var xPathLink = '//div[@class="box"]/div[@class="box-header"]/h4';
driver.findElement(seleniumWebdriver.By.xpath(xPathLink))
.then( function (element) {
element.getText('h4')
.then( function (text) {
//expected value of the title text
var expectedText = 'Featured Projects';
t.equal(text, expectedText);
t.end();
});
});
});
//checks that the link for a project makes sense
tap.test('checkFeaturedProjectsRowLinkWhenSignedOut', function (t) {
var xPathLink = '//div[contains(@class, "thumbnail") ' +
'and contains(@class, "project") and contains(@class, "slick-slide") ' +
'and contains(@class, "slick-active")]/a[@class="thumbnail-image"]';
driver.wait(seleniumWebdriver.until
.elementLocated(seleniumWebdriver.By.xpath(xPathLink)))
.then( function (element) {
element.getAttribute('href')
.then( function (url) {
//expected pattern for the project URL
//since I don't know the length of the project ID number
var expectedUrlRegExp = new RegExp('/projects/.*[0-9].*/?');
t.match(url, expectedUrlRegExp);
t.end();
});
});
});
//checks that the title of the 2nd row is Featured Studios
tap.test('checkFeaturedStudiosRowWhenSignedOut', function (t) {
var xPathLink = '//div[@class="box"][2]/div[@class="box-header"]/h4';
driver.findElement(seleniumWebdriver.By.xpath(xPathLink))
.then(function (element) {
element.getText('h4')
.then( function (text) {
var expectedText = 'Featured Studios';
t.equal(text, expectedText);
t.end();
});
});
});
//checks that the link for a studio makes sense
tap.test('checkFeaturedStudiosRowLinkWhenSignedOut', function (t) {
var xPathLink = '//div[contains(@class, "thumbnail") and contains(@class, "gallery") ' +
'and contains(@class, "slick-slide") and contains(@class, "slick-active")]/a[@class="thumbnail-image"]';
driver.findElement(seleniumWebdriver.By.xpath(xPathLink))
.then(function (element) {
element.getAttribute('href')
.then( function (url) {
var expectedUrlRegExp = new RegExp('/studios/.*[0-9].*/?');
t.match(url, expectedUrlRegExp);
t.end();
});
});
});

View file

@ -0,0 +1,103 @@
var defaults = require('lodash.defaults');
var fastlyConfig = require('../../bin/lib/fastly-config-methods');
var route_json = require('../../src/routes.json');
var tap = require('tap');
var testRoutes = [
{
name: 'less-traveled',
pattern: '^/?$',
routeAlias: '/?$',
view: 'less-traveled/less-traveled',
title: 'Robert Frost Goes Here'
},
{
name: 'more-traveled',
pattern: '^/more?$',
routeAlias: '/more?$',
view: 'more-traveled/more-traveled',
title: 'Robert Frost Does Not Go Here'
}
];
var routes = route_json.map(function (route) {
return defaults({}, {pattern: fastlyConfig.expressPatternToRegex(route.pattern)}, route);
});
var extraAppRoutes = [
// Homepage with querystring.
// TODO: Should this be added for every route?
'/\\?',
// View html
'/[^\/]*\.html$'
];
tap.test('getStaticPaths', function (t) {
var staticPaths = fastlyConfig.getStaticPaths(__dirname, '../../build/*');
t.type(staticPaths, 'object');
t.end();
});
tap.test('getViewPaths', function (t) {
var viewPaths = fastlyConfig.getViewPaths(testRoutes);
t.type(viewPaths, 'object');
t.equal(viewPaths[0], '/?$');
t.equal(viewPaths[1], '/more?$');
t.end();
});
tap.test('pathsToCondition', function (t) {
var condition = fastlyConfig.pathsToCondition(['/?$', '/more?$']);
t.type(condition, 'string');
t.equal(condition, 'req.url~"^(/?$|/more?$)"');
t.end();
});
tap.test('getAppRouteCondition', function (t) {
var condition = fastlyConfig.getAppRouteCondition('../../build/*', routes, extraAppRoutes, __dirname);
t.type(condition, 'string');
t.end();
});
tap.test('testSetBackend', function (t) {
var backend = fastlyConfig.setBackend('wemust', 'goback', 'marty');
t.equal(backend, '' +
'if (marty) {\n' +
' set req.backend = wemust;\n' +
' set req.http.host = \"goback\";\n' +
'}\n'
);
t.end();
});
tap.test('testSetForward', function (t) {
var forward = fastlyConfig.setForwardHeaders('alwaysforward');
t.equal(forward, '' +
'if (alwaysforward) {\n' +
' if (!req.http.Fastly-FF) {\n' +
' if (req.http.X-Forwarded-For) {\n' +
' set req.http.Fastly-Temp-XFF = req.http.X-Forwarded-For ", " client.ip;\n' +
' } else {\n' +
' set req.http.Fastly-Temp-XFF = client.ip;\n' +
' }\n' +
' } else {\n' +
' set req.http.Fastly-Temp-XFF = req.http.X-Forwarded-For;\n' +
' }\n' +
' set req.grace = 60s;\n' +
' return(pass);\n' +
'}\n'
);
t.end();
});
tap.test('testSetTTL', function (t) {
var ttl = fastlyConfig.setResponseTTL('itsactuallyttyl');
t.equal(ttl, '' +
'if (itsactuallyttyl) {\n' +
' set beresp.ttl = 0s;\n' +
' set beresp.grace = 0s;\n' +
' return(pass);\n' +
'}\n'
);
t.end();
});