<commit_before>const URL = require('url');
const ghGot = require('gh-got');
const delay = require('delay');
const parseLinkHeader = require('parse-link-header');

let cache = {};

async function get(path, opts, retries = 5) {
  /* eslint-disable no-param-reassign */
  opts = Object.assign({}, opts);
  const method = opts.method ? opts.method : 'get';
  if (method === 'get' && cache[path]) {
    logger.trace({ path }, 'Returning cached result');
    return cache[path];
  }
  logger.debug(`${method.toUpperCase()} ${path} [retries=${retries}]`);
  try {
    if (appMode) {
      const appAccept = 'application/vnd.github.machine-man-preview+json';
      opts.headers = Object.assign(
        {},
        {
          accept: appAccept,
          'user-agent': 'https://github.com/renovateapp/renovate',
        },
        opts.headers
      );
      if (opts.headers.accept !== appAccept) {
        opts.headers.accept = `${appAccept}, ${opts.headers.accept}`;
      }
    }
    const res = await ghGot(path, opts);
    if (opts.paginate) {
      // Check if result is paginated
      const linkHeader = parseLinkHeader(res.headers.link);
      if (linkHeader && linkHeader.next && linkHeader.last) {
        let lastPage = +linkHeader.last.page;
        if (!process.env.RENOVATE_PAGINATE_ALL) {
          lastPage = Math.min(10, lastPage);
        }
        const pageNumbers = Array.from(
          new Array(lastPage),
          (x, i) => i + 1
        ).slice(1);
        const pages = await Promise.all(
          pageNumbers.map(page => {
            const url = URL.parse(linkHeader.next.url, true);
            delete url.search;
            url.query.page = page;
            return get(URL.format(url), { ...opts, paginate: false }, retries);
          })
        );
        res.body = res.body.concat(
          ...pages.filter(Boolean).map(page => page.body)
        );
      }
    }
    if (
      method === 'get' &&
      (path.startsWith('repos/') ||
        path.startsWith('https://api.github.com/repos/'))
    ) {
      cache[path] = res;
    }
    return res;
  } catch (err) {
    if (err.statusCode >= 500 && err.statusCode < 600 && retries > 0) {
      logger.info(
        { statusCode: err.statusCode, message: err.message },
        `Retrying request`
      );

      await delay(5000 / retries);

      return get(path, opts, retries - 1);
    } else if (
      retries > 0 &&
      err.statusCode === 403 &&
      err.message &&
      err.message.startsWith('You have triggered an abuse detection mechanism')
    ) {
      logger.info(
        { statusCode: err.statusCode, message: err.message },
        `Retrying request`
      );

      await delay(180000 / (retries * retries));

      return get(path, opts, retries - 1);
    } else if (
      err.statusCode === 403 &&
      err.message &&
      err.message.includes('rate limit exceeded')
    ) {
      logger.info({ err }, 'Rate limit exceeded');
      throw new Error('rate-limit-exceeded');
    } else if (
      err.statusCode === 403 &&
      err.message &&
      err.message.includes('blobs up to 1 MB in size')
    ) {
      throw err;
    } else if (err.statusCode === 403) {
      if (retries > 0) {
        logger.info(
          { statusCode: err.statusCode, message: err.message },
          `Retrying request`
        );

        await delay(60000 / (retries * retries));

        return get(path, opts, retries - 1);
      }
    } else if (
      err.statusCode === 401 &&
      err.message &&
      err.message.includes('Bad credentials')
    ) {
      logger.debug(
        {
          err,
          message: err.message,
          body: err.response ? err.response.body : undefined,
        },
        'Bad credentials'
      );
      throw new Error('bad-credentials');
    }
    throw err;
  }
}

const helpers = ['get', 'post', 'put', 'patch', 'head', 'delete'];

for (const x of helpers) {
  get[x] = (url, opts) =>
    get(url, Object.assign({}, opts, { method: x.toUpperCase() }));
}

let appMode = false;
get.setAppMode = function setAppMode(val) {
  appMode = val;
};

get.reset = function reset() {
  cache = null;
  cache = {};
};

module.exports = get;
<commit_msg>refactor(github): log rate limit when receiving bad-credentials<commit_after>const URL = require('url');
const ghGot = require('gh-got');
const delay = require('delay');
const parseLinkHeader = require('parse-link-header');

let cache = {};

async function get(path, opts, retries = 5) {
  /* eslint-disable no-param-reassign */
  opts = Object.assign({}, opts);
  const method = opts.method ? opts.method : 'get';
  if (method === 'get' && cache[path]) {
    logger.trace({ path }, 'Returning cached result');
    return cache[path];
  }
  logger.debug(`${method.toUpperCase()} ${path} [retries=${retries}]`);
  try {
    if (appMode) {
      const appAccept = 'application/vnd.github.machine-man-preview+json';
      opts.headers = Object.assign(
        {},
        {
          accept: appAccept,
          'user-agent': 'https://github.com/renovateapp/renovate',
        },
        opts.headers
      );
      if (opts.headers.accept !== appAccept) {
        opts.headers.accept = `${appAccept}, ${opts.headers.accept}`;
      }
    }
    const res = await ghGot(path, opts);
    if (opts.paginate) {
      // Check if result is paginated
      const linkHeader = parseLinkHeader(res.headers.link);
      if (linkHeader && linkHeader.next && linkHeader.last) {
        let lastPage = +linkHeader.last.page;
        if (!process.env.RENOVATE_PAGINATE_ALL) {
          lastPage = Math.min(10, lastPage);
        }
        const pageNumbers = Array.from(
          new Array(lastPage),
          (x, i) => i + 1
        ).slice(1);
        const pages = await Promise.all(
          pageNumbers.map(page => {
            const url = URL.parse(linkHeader.next.url, true);
            delete url.search;
            url.query.page = page;
            return get(URL.format(url), { ...opts, paginate: false }, retries);
          })
        );
        res.body = res.body.concat(
          ...pages.filter(Boolean).map(page => page.body)
        );
      }
    }
    if (
      method === 'get' &&
      (path.startsWith('repos/') ||
        path.startsWith('https://api.github.com/repos/'))
    ) {
      cache[path] = res;
    }
    return res;
  } catch (err) {
    if (err.statusCode >= 500 && err.statusCode < 600 && retries > 0) {
      logger.info(
        { statusCode: err.statusCode, message: err.message },
        `Retrying request`
      );

      await delay(5000 / retries);

      return get(path, opts, retries - 1);
    } else if (
      retries > 0 &&
      err.statusCode === 403 &&
      err.message &&
      err.message.startsWith('You have triggered an abuse detection mechanism')
    ) {
      logger.info(
        { statusCode: err.statusCode, message: err.message },
        `Retrying request`
      );

      await delay(180000 / (retries * retries));

      return get(path, opts, retries - 1);
    } else if (
      err.statusCode === 403 &&
      err.message &&
      err.message.includes('rate limit exceeded')
    ) {
      logger.info({ err }, 'Rate limit exceeded');
      throw new Error('rate-limit-exceeded');
    } else if (
      err.statusCode === 403 &&
      err.message &&
      err.message.includes('blobs up to 1 MB in size')
    ) {
      throw err;
    } else if (err.statusCode === 403) {
      if (retries > 0) {
        logger.info(
          { statusCode: err.statusCode, message: err.message },
          `Retrying request`
        );

        await delay(60000 / (retries * retries));

        return get(path, opts, retries - 1);
      }
    } else if (
      err.statusCode === 401 &&
      err.message &&
      err.message.includes('Bad credentials')
    ) {
      logger.info(
        {
          err,
          message: err.message,
          rateLimit: err.headers ? err.headers['x-ratelimit-limit'] : -1,
          body: err.response ? err.response.body : undefined,
        },
        'Bad credentials'
      );
      throw new Error('bad-credentials');
    }
    throw err;
  }
}

const helpers = ['get', 'post', 'put', 'patch', 'head', 'delete'];

for (const x of helpers) {
  get[x] = (url, opts) =>
    get(url, Object.assign({}, opts, { method: x.toUpperCase() }));
}

let appMode = false;
get.setAppMode = function setAppMode(val) {
  appMode = val;
};

get.reset = function reset() {
  cache = null;
  cache = {};
};

module.exports = get;
<|endoftext|>


<commit_before>const URL = require('url');
const ghGot = require('gh-got');
const delay = require('delay');
const parseLinkHeader = require('parse-link-header');
const endpoints = require('../../util/endpoints');

let cache = {};
let stats = {};

async function get(path, options, retries = 5) {
  const { host } = URL.parse(path);
  const opts = {
    ...endpoints.find({ platform: 'github', host }),
    ...options,
  };
  const method = opts.method || 'get';
  const useCache = opts.useCache || true;
  if (method === 'get' && useCache && cache[path]) {
    logger.trace({ path }, 'Returning cached result');
    return cache[path];
  }
  logger.trace(`${method.toUpperCase()} ${path}`);
  stats.requests = (stats.requests || []).concat([
    method.toUpperCase() + ' ' + path.replace(opts.endpoint, ''),
  ]);
  try {
    if (global.appMode) {
      const appAccept = 'application/vnd.github.machine-man-preview+json';
      opts.headers = Object.assign(
        {},
        {
          accept: appAccept,
          'user-agent':
            process.env.RENOVATE_USER_AGENT ||
            'https://github.com/renovatebot/renovate',
        },
        opts.headers
      );
      if (opts.headers.accept !== appAccept) {
        opts.headers.accept = `${appAccept}, ${opts.headers.accept}`;
      }
    }
    const res = await ghGot(path, opts);
    if (res && res.headers) {
      stats.rateLimit = res.headers['x-ratelimit-limit'];
      stats.rateLimitRemaining = res.headers['x-ratelimit-remaining'];
    }
    if (opts.paginate) {
      // Check if result is paginated
      const pageLimit = opts.pageLimit || 10;
      const linkHeader = parseLinkHeader(res.headers.link);
      if (linkHeader && linkHeader.next && linkHeader.last) {
        let lastPage = +linkHeader.last.page;
        if (!process.env.RENOVATE_PAGINATE_ALL) {
          lastPage = Math.min(pageLimit, lastPage);
        }
        const pageNumbers = Array.from(
          new Array(lastPage),
          (x, i) => i + 1
        ).slice(1);
        const pages = await Promise.all(
          pageNumbers.map(page => {
            const url = URL.parse(linkHeader.next.url, true);
            delete url.search;
            url.query.page = page;
            return get(URL.format(url), { ...opts, paginate: false }, retries);
          })
        );
        res.body = res.body.concat(
          ...pages.filter(Boolean).map(page => page.body)
        );
      }
    }
    if (
      method === 'get' &&
      (path.startsWith('repos/') ||
        path.startsWith('https://api.github.com/repos/'))
    ) {
      cache[path] = res;
    }
    // istanbul ignore if
    if (method === 'POST' && path === 'graphql') {
      const goodResult = '{"data":{';
      if (res.body.startsWith(goodResult)) {
        if (retries === 0) {
          logger.info('Recovered graphql query');
        }
      } else if (retries > 0) {
        logger.info('Retrying graphql query');
        opts.body = opts.body.replace('first: 100', 'first: 50');
        return get(path, opts, 0);
      }
    }
    return res;
  } catch (err) {
    if (
      err.name === 'RequestError' &&
      (err.code === 'ENOTFOUND' || err.code === 'ETIMEDOUT')
    ) {
      throw new Error('platform-failure');
    }
    if (err.statusCode >= 500 && err.statusCode < 600) {
      if (retries > 0) {
        logger.info(
          { statusCode: err.statusCode, message: err.message },
          `Retrying request`
        );

        await delay(5000 / retries);

        return get(path, opts, retries - 1);
      }
      throw new Error('platform-failure');
    }
    if (
      retries > 0 &&
      err.statusCode === 403 &&
      err.message &&
      err.message.startsWith('You have triggered an abuse detection mechanism')
    ) {
      logger.info(
        {
          headers: err.headers,
          path,
          statusCode: err.statusCode,
          message: err.message,
        },
        `Retrying request`
      );

      await delay(180000 / (retries * retries));

      return get(path, opts, retries - 1);
    }
    if (
      err.statusCode === 403 &&
      err.message &&
      err.message.includes('rate limit exceeded')
    ) {
      logger.info({ err, headers: err.headers }, 'Rate limit exceeded');
      throw new Error('rate-limit-exceeded');
    } else if (
      err.statusCode === 403 &&
      err.message &&
      err.message.includes('blobs up to 1 MB in size')
    ) {
      throw err;
    } else if (err.statusCode === 403) {
      if (retries > 0) {
        logger.info(
          { statusCode: err.statusCode, message: err.message },
          `Retrying request`
        );

        await delay(60000 / (retries * retries));

        return get(path, opts, retries - 1);
      }
    } else if (
      err.statusCode === 401 &&
      err.message &&
      err.message.includes('Bad credentials')
    ) {
      const rateLimit = err.headers ? err.headers['x-ratelimit-limit'] : -1;
      logger.info(
        {
          token: maskToken(opts.token),
          err,
        },
        'Bad credentials'
      );
      if (rateLimit === '60') {
        throw new Error('platform-failure');
      }
      throw new Error('bad-credentials');
    }
    throw err;
  }
}

// istanbul ignore next
function maskToken(token) {
  if (!token) {
    return token;
  }
  return `${token.substring(0, 2)}${new Array(token.length - 3).join(
    '*'
  )}${token.slice(-2)}`;
}

const helpers = ['get', 'post', 'put', 'patch', 'head', 'delete'];

for (const x of helpers) {
  get[x] = (url, opts) =>
    get(url, Object.assign({}, opts, { method: x.toUpperCase() }));
}

get.setAppMode = function setAppMode() {
  // no-op
};

get.reset = function reset() {
  cache = null;
  cache = {};
  // istanbul ignore if
  if (stats.requests && stats.requests.length > 1) {
    logger.info(
      {
        rateLimit: parseInt(stats.rateLimit, 10),
        requestCount: stats.requests.length,
        rateLimitRemaining: parseInt(stats.rateLimitRemaining, 10),
      },
      'Request stats'
    );
    stats.requests.sort();
    logger.debug({ requests: stats.requests }, 'All requests');
    stats = {};
  }
};

module.exports = get;
<commit_msg>refactor(github): reduce graphql retry to 25<commit_after>const URL = require('url');
const ghGot = require('gh-got');
const delay = require('delay');
const parseLinkHeader = require('parse-link-header');
const endpoints = require('../../util/endpoints');

let cache = {};
let stats = {};

async function get(path, options, retries = 5) {
  const { host } = URL.parse(path);
  const opts = {
    ...endpoints.find({ platform: 'github', host }),
    ...options,
  };
  const method = opts.method || 'get';
  const useCache = opts.useCache || true;
  if (method === 'get' && useCache && cache[path]) {
    logger.trace({ path }, 'Returning cached result');
    return cache[path];
  }
  logger.trace(`${method.toUpperCase()} ${path}`);
  stats.requests = (stats.requests || []).concat([
    method.toUpperCase() + ' ' + path.replace(opts.endpoint, ''),
  ]);
  try {
    if (global.appMode) {
      const appAccept = 'application/vnd.github.machine-man-preview+json';
      opts.headers = Object.assign(
        {},
        {
          accept: appAccept,
          'user-agent':
            process.env.RENOVATE_USER_AGENT ||
            'https://github.com/renovatebot/renovate',
        },
        opts.headers
      );
      if (opts.headers.accept !== appAccept) {
        opts.headers.accept = `${appAccept}, ${opts.headers.accept}`;
      }
    }
    const res = await ghGot(path, opts);
    if (res && res.headers) {
      stats.rateLimit = res.headers['x-ratelimit-limit'];
      stats.rateLimitRemaining = res.headers['x-ratelimit-remaining'];
    }
    if (opts.paginate) {
      // Check if result is paginated
      const pageLimit = opts.pageLimit || 10;
      const linkHeader = parseLinkHeader(res.headers.link);
      if (linkHeader && linkHeader.next && linkHeader.last) {
        let lastPage = +linkHeader.last.page;
        if (!process.env.RENOVATE_PAGINATE_ALL) {
          lastPage = Math.min(pageLimit, lastPage);
        }
        const pageNumbers = Array.from(
          new Array(lastPage),
          (x, i) => i + 1
        ).slice(1);
        const pages = await Promise.all(
          pageNumbers.map(page => {
            const url = URL.parse(linkHeader.next.url, true);
            delete url.search;
            url.query.page = page;
            return get(URL.format(url), { ...opts, paginate: false }, retries);
          })
        );
        res.body = res.body.concat(
          ...pages.filter(Boolean).map(page => page.body)
        );
      }
    }
    if (
      method === 'get' &&
      (path.startsWith('repos/') ||
        path.startsWith('https://api.github.com/repos/'))
    ) {
      cache[path] = res;
    }
    // istanbul ignore if
    if (method === 'POST' && path === 'graphql') {
      const goodResult = '{"data":{';
      if (res.body.startsWith(goodResult)) {
        if (retries === 0) {
          logger.info('Recovered graphql query');
        }
      } else if (retries > 0) {
        logger.info('Retrying graphql query');
        opts.body = opts.body.replace('first: 100', 'first: 25');
        return get(path, opts, 0);
      }
    }
    return res;
  } catch (err) {
    if (
      err.name === 'RequestError' &&
      (err.code === 'ENOTFOUND' || err.code === 'ETIMEDOUT')
    ) {
      throw new Error('platform-failure');
    }
    if (err.statusCode >= 500 && err.statusCode < 600) {
      if (retries > 0) {
        logger.info(
          { statusCode: err.statusCode, message: err.message },
          `Retrying request`
        );

        await delay(5000 / retries);

        return get(path, opts, retries - 1);
      }
      throw new Error('platform-failure');
    }
    if (
      retries > 0 &&
      err.statusCode === 403 &&
      err.message &&
      err.message.startsWith('You have triggered an abuse detection mechanism')
    ) {
      logger.info(
        {
          headers: err.headers,
          path,
          statusCode: err.statusCode,
          message: err.message,
        },
        `Retrying request`
      );

      await delay(180000 / (retries * retries));

      return get(path, opts, retries - 1);
    }
    if (
      err.statusCode === 403 &&
      err.message &&
      err.message.includes('rate limit exceeded')
    ) {
      logger.info({ err, headers: err.headers }, 'Rate limit exceeded');
      throw new Error('rate-limit-exceeded');
    } else if (
      err.statusCode === 403 &&
      err.message &&
      err.message.includes('blobs up to 1 MB in size')
    ) {
      throw err;
    } else if (err.statusCode === 403) {
      if (retries > 0) {
        logger.info(
          { statusCode: err.statusCode, message: err.message },
          `Retrying request`
        );

        await delay(60000 / (retries * retries));

        return get(path, opts, retries - 1);
      }
    } else if (
      err.statusCode === 401 &&
      err.message &&
      err.message.includes('Bad credentials')
    ) {
      const rateLimit = err.headers ? err.headers['x-ratelimit-limit'] : -1;
      logger.info(
        {
          token: maskToken(opts.token),
          err,
        },
        'Bad credentials'
      );
      if (rateLimit === '60') {
        throw new Error('platform-failure');
      }
      throw new Error('bad-credentials');
    }
    throw err;
  }
}

// istanbul ignore next
function maskToken(token) {
  if (!token) {
    return token;
  }
  return `${token.substring(0, 2)}${new Array(token.length - 3).join(
    '*'
  )}${token.slice(-2)}`;
}

const helpers = ['get', 'post', 'put', 'patch', 'head', 'delete'];

for (const x of helpers) {
  get[x] = (url, opts) =>
    get(url, Object.assign({}, opts, { method: x.toUpperCase() }));
}

get.setAppMode = function setAppMode() {
  // no-op
};

get.reset = function reset() {
  cache = null;
  cache = {};
  // istanbul ignore if
  if (stats.requests && stats.requests.length > 1) {
    logger.info(
      {
        rateLimit: parseInt(stats.rateLimit, 10),
        requestCount: stats.requests.length,
        rateLimitRemaining: parseInt(stats.rateLimitRemaining, 10),
      },
      'Request stats'
    );
    stats.requests.sort();
    logger.debug({ requests: stats.requests }, 'All requests');
    stats = {};
  }
};

module.exports = get;
<|endoftext|>


<commit_before>'use strict'; // eslint-disable-line strict

const parseIp = require('../ipCheck').parseIp;
// http://docs.aws.amazon.com/IAM/latest/UserGuide/list_s3.html
// For MPU actions:
// http://docs.aws.amazon.com/AmazonS3/latest/dev/mpuAndPermissions.html
// For bucket head and object head:
// http://docs.aws.amazon.com/AmazonS3/latest/dev/
// using-with-s3-actions.html
const _actionMap = {
    bucketDelete: 's3:DeleteBucket',
    bucketGet: 's3:ListBucket',
    bucketGetACL: 's3:GetBucketAcl',
    bucketGetVersioning: 's3:GetBucketVersioning',
    bucketHead: 's3:ListBucket',
    bucketPut: 's3:CreateBucket',
    bucketPutACL: 's3:PutBucketAcl',
    bucketPutVersioning: 's3:PutBucketVersioning',
    completeMultipartUpload: 's3:PutObject',
    initiateMultipartUpload: 's3:PutObject',
    listMultipartUploads: 's3:ListBucketMultipartUploads',
    listParts: 's3:ListMultipartUploadParts',
    multipartDelete: 's3:AbortMultipartUpload',
    objectDelete: 's3:DeleteObject',
    objectGet: 's3:GetObject',
    objectGetACL: 's3:GetObjectAcl',
    objectHead: 's3:GetObject',
    objectPut: 's3:PutObject',
    objectPutACL: 's3:PutObjectAcl',
    objectPutPart: 's3:PutObject',
    serviceGet: 's3:ListAllMyBuckets',
};

const _actionMapIAM = {
    attachGroupPolicy: 'iam:AttachGroupPolicy',
    attachUserPolicy: 'iam:AttachUserPolicy',
    createAccessKey: 'iam:CreateAccessKey',
    createGroup: 'iam:CreateGroup',
    createPolicy: 'iam:CreatePolicy',
    createPolicyVersion: 'iam:CreatePolicyVersion',
    createUser: 'iam:CreateUser',
    deleteAccessKey: 'iam:DeleteAccessKey',
    deleteGroup: 'iam:DeleteGroup',
    deleteGroupPolicy: 'iam:DeleteGroupPolicy',
    deletePolicy: 'iam:DeletePolicy',
    deletePolicyVersion: 'iam:DeletePolicyVersion',
    deleteUser: 'iam:DeleteUser',
    detachGroupPolicy: 'iam:DetachGroupPolicy',
    detachUserPolicy: 'iam:DetachUserPolicy',
    getGroup: 'iam:GetGroup',
    getGroupPolicy: 'iam:GetGroupPolicy',
    getPolicy: 'iam:GetPolicy',
    getPolicyVersion: 'iam:GetPolicyVersion',
    getUser: 'iam:GetUser',
    listAccessKeys: 'iam:ListAccessKeys',
    listGroupPolicies: 'iam:ListGroupPolicies',
    listGroups: 'iam:ListGroups',
    listGroupsForUser: 'iam:ListGroupsForUser',
    listPolicies: 'iam:ListPolicies',
    listPolicyVersions: 'iam:ListPolicyVersions',
    listUsers: 'iam:ListUsers',
    putGroupPolicy: 'iam:PutGroupPolicy',
    removeUserFromGroup: 'iam:RemoveUserFromGroup',
};

function _findAction(service, method) {
    if (service === 's3') {
        return _actionMap[method];
    }
    if (service === 'iam') {
        return _actionMapIAM[method];
    }
    if (service === 'utapi') {
        // currently only method is ListMetrics
        return `utapi:${method}`;
    }
    return undefined;
}

function _buildArn(service, generalResource, specificResource, requesterInfo) {
      // arn:partition:service:region:account-id:resourcetype/resource
    if (service === 's3') {
        // arn:aws:s3:::bucket/object
        // General resource is bucketName
        if (generalResource && specificResource) {
            return `arn:aws:s3:::${generalResource}/${specificResource}`;
        } else if (generalResource) {
            return `arn:aws:s3:::${generalResource}`;
        }
        return 'arn:aws:s3:::';
    }
    if (service === 'iam') {
        // arn:aws:iam::<account-id>:<resource-type><resource>
        if (specificResource) {
            return `arn:aws:iam::${requesterInfo.accountid}:` +
                `${generalResource}${specificResource}`;
        }
        return `arn:aws:iam::${requesterInfo.accountid}:${generalResource}`;
    }
    if (service === 'utapi') {
        // arn:scality:utapi:::resourcetype/resource
        // (possible resource types are buckets, accounts or users)
        if (specificResource) {
            return `arn:scality:utapi:::${generalResource}/${specificResource}`;
        }
        return `arn:scality:utapi:::${generalResource}/`;
    }
    return undefined;
}

/**
 * Class containing RequestContext for policy auth check
 * @param {object} headers - request headers
 * @param {query} query - request query
 * @param {string} generalResource - bucket name from request if any if from s3
 * or accounts, buckets or users from utapi
 * @param {string} specificResource - object name from request if any if from s3
 * or bucketname from utapi if from utapi
 * @param {string} requesterIp - ip of requester
 * @param {boolean} sslEnabled - whether request was https
 * @param {string} apiMethod - type of request
 * @param {string} awsService - service receiving request
 * @param {string} locationConstraint - location constraint
 * for put bucket operation
 * @param {object} requesterInfo - info about entity making request
 * @param {string} signatureVersion - auth signature type used
 * @param {string} authType - type of authentication used
 * @param {number} signatureAge - age of signature in milliseconds
 * @return {RequestContext} a RequestContext instance
 */

class RequestContext {
    constructor(headers, query, generalResource, specificResource,
        requesterIp, sslEnabled, apiMethod,
        awsService, locationConstraint, requesterInfo,
        signatureVersion, authType, signatureAge) {
        this._headers = headers;
        this._query = query;
        this._requesterIp = requesterIp;
        this._sslEnabled = sslEnabled;
        this._apiMethod = apiMethod;
        this._awsService = awsService;
        this._generalResource = generalResource;
        this._specificResource = specificResource;
        this._locationConstraint = locationConstraint;
        // Not implemented
        this._multiFactorAuthPresent = null;
        // Not implemented
        this._multiFactorAuthAge = null;
        // Not implemented
        this._tokenIssueTime = null;

        // Remainder not set when originally instantiated
        // (unless if instantiated from deSerialize)
        this._requesterInfo = requesterInfo;
        // See http://docs.aws.amazon.com/AmazonS3/latest/
        // API/bucket-policy-s3-sigv4-conditions.html
        this._signatureVersion = signatureVersion;
        this._authType = authType;
        this._signatureAge = signatureAge;

        return this;
    }

    /**
    * Serialize the object
    * @return {string} - stringified object
    */
    serialize() {
        const requestInfo = {
            apiMethod: this._apiMethod,
            headers: this._headers,
            query: this._query,
            requersterInfo: this._requesterInfo,
            requesterIp: this._requesterIp,
            sslEnabled: this._sslEnabled,
            awsService: this._awsService,
            generalResource: this._generalResource,
            specificResource: this._specificResource,
            multiFactorAuthPresent: this._multiFactorAuthPresent,
            multiFactorAuthAge: this._multiFactorAuthAge,
            signatureVersion: this._signatureVersion,
            authType: this._authType,
            signatureAge: this._signatureAge,
            locationConstraint: this._locationConstraint,
            tokenIssueTime: this._tokenIssueTime,
        };
        return JSON.stringify(requestInfo);
    }

    /**
     * deSerialize the JSON string
     * @param {string} stringRequest - the stringified requestContext
     * @return {object} - parsed string
     */
    static deSerialize(stringRequest) {
        let obj;
        try {
            obj = JSON.parse(stringRequest);
        } catch (err) {
            return new Error(err);
        }
        return new RequestContext(obj.headers, obj.query, obj.generalResource,
            obj.specificResource, obj.requesterIp, obj.sslEnabled,
            obj.apiMethod, obj.awsService, obj.locationConstraint,
            obj.requesterInfo, obj.signatureVersion,
            obj.authType, obj.signatureAge);
    }

    /**
    * Get the request action
    * @return {string} action
    */
    getAction() {
        if (this._foundAction) {
            return this._foundAction;
        }
        this._foundAction = _findAction(this._awsService, this._apiMethod);
        return this._foundAction;
    }

    /**
    * Get the resource impacted by the request
    * @return {string} arn for the resource
    */
    getResource() {
        if (this._foundResource) {
            return this._foundResource;
        }
        this._foundResource =
            _buildArn(this._awsService, this._generalResource,
                this._specificResource, this._requesterInfo);
        return this._foundResource;
    }

    /**
     * Set headers
     * @param {object} headers - request headers
     * @return {RequestContext} - RequestContext instance
     */
    setHeaders(headers) {
        this._headers = headers;
        return this;
    }
    /**
     * Get headers
     * @return {object} request headers
     */
    getHeaders() {
        return this._headers;
    }

    /**
     * Set query
     * @param {object} query - request query
     * @return {RequestContext} - RequestContext instance
     */
    setQuery(query) {
        this._query = query;
        return this;
    }
    /**
     * Get query
     * @return {object} request query
     */
    getQuery() {
        return this._query;
    }

    /**
     * Set requesterInfo
     * @param {object} requesterInfo - info about entity making request
     * @return {RequestContext} - RequestContext instance
     */
    setRequesterInfo(requesterInfo) {
        this._requesterInfo = requesterInfo;
        return this;
    }
    /**
     * Get requesterInfo
     * @return {object} requesterInfo
     */
    getRequesterInfo() {
        return this._requesterInfo;
    }

    /**
     * Set requesterIp
     * @param {string} requesterIp - ip address of requester
     * @return {RequestContext} - RequestContext instance
     */
    setRequesterIp(requesterIp) {
        this._requesterIp = requesterIp;
        return this;
    }
    /**
     * Get requesterIp
     * @return {object} requesterIp - parsed requesterIp
     */
    getRequesterIp() {
        return parseIp(this._requesterIp);
    }

    /**
     * Set sslEnabled
     * @param {boolean} sslEnabled - true if https used
     * @return {RequestContext} - RequestContext instance
     */
    setSslEnabled(sslEnabled) {
        this._sslEnabled = sslEnabled;
        return this;
    }

    /**
     * Get sslEnabled
     * @return {boolean} true if sslEnabled, false if not
     */
    getSslEnabled() {
        return !!this._sslEnabled;
    }

    /**
     * Set signatureVersion
     * @param {string} signatureVersion - "AWS" identifies Signature Version 2
     * and "AWS4-HMAC-SHA256" identifies Signature Version 4
     * @return {RequestContext} - RequestContext instance
     */
    setSignatureVersion(signatureVersion) {
        this._signatureVersion = signatureVersion;
        return this;
    }

    /**
     * Get signatureVersion
     *
     * @return {string} authentication signature version
     * "AWS" identifies Signature Version 2 and
     * "AWS4-HMAC-SHA256" identifies Signature Version 4
     */
    getSignatureVersion() {
        return this._signatureVersion;
    }

    /**
     * Set authType
     * @param {string} authType - REST-HEADER, REST-QUERY-STRING or POST
     * @return {RequestContext} - RequestContext instance
     */
    setAuthType(authType) {
        this._authType = authType;
        return this;
    }

    /**
     * Get authType
     * @return {string} authentication type:
     * REST-HEADER, REST-QUERY-STRING or POST
     */
    getAuthType() {
        return this._authType;
    }

    /**
     * Set signatureAge
     * @param {number} signatureAge -- age of signature in milliseconds
     * Note that for v2 query auth this will be undefined (since these
     * requests are pre-signed and only come with an expires time so
     * do not know age)
     * @return {RequestContext} - RequestContext instance
     */
    setSignatureAge(signatureAge) {
        this._signatureAge = signatureAge;
        return this;
    }
    /**
     * Get signatureAge
     * @return {number} age of signature in milliseconds
     * Note that for v2 query auth this will be undefined (since these
     * requests are pre-signed and only come with an expires time so
     * do not know age)
     */
    getSignatureAge() {
        return this._signatureAge;
    }

    /**
     * Set locationConstraint
     * @param {string} locationConstraint - bucket region constraint
     * @return {RequestContext} - RequestContext instance
     */
    setLocationConstraint(locationConstraint) {
        this._locationConstraint = locationConstraint;
        return this;
    }
    /**
     * Get locationConstraint
     * @return {string} location constraint of put bucket request
     */
    getLocationConstraint() {
        return this._locationConstraint;
    }

    /**
     * Set awsService
     * @param {string} awsService receiving request
     * @return {RequestContext} - RequestContext instance
     */
    setAwsService(awsService) {
        this._awsService = awsService;
        return this;
    }
    /**
     * Get awsService
     * @return {string} awsService receiving request
     */
    getAwsService() {
        return this._awsService;
    }

    /**
     * Set tokenIssueTime
     * @param {string} tokenIssueTime - Date/time that
     * temporary security credentials were issued
     * Only present in requests that are signed using
     * temporary security credentials.
     * @return {RequestContext} - RequestContext instance
     */
    setTokenIssueTime(tokenIssueTime) {
        this._tokenIssueTime = tokenIssueTime;
        return this;
    }
    /**
     * Get tokenIssueTime
     * @return {string} tokenIssueTime
     */
    getTokenIssueTime() {
        return this._tokenIssueTime;
    }

    /**
     * Set multiFactorAuthPresent
     * @param {boolean} multiFactorAuthPresent - sets out whether MFA used
     * for request
     * @return {RequestContext} - RequestContext instance
     */
    setMultiFactorAuthPresent(multiFactorAuthPresent) {
        this._multiFactorAuthPresent = multiFactorAuthPresent;
        return this;
    }
    /**
     * Get multiFactorAuthPresent
     * @return {boolean} multiFactorAuthPresent
     */
    getMultiFactorAuthPresent() {
        return this._multiFactorAuthPresent;
    }

    /**
     * Set multiFactorAuthAge
     * @param {number} multiFactorAuthAge - seconds since
     * MFA credentials were issued
     * @return {RequestContext} - RequestContext instance
     */
    setMultiFactorAuthAge(multiFactorAuthAge) {
        this._multiFactorAuthAge = multiFactorAuthAge;
        return this;
    }
    /**
     * Get multiFactorAuthAge
     * @return {number} multiFactorAuthAge - seconds since
     *  MFA credentials were issued
     */
    getMultiFactorAuthAge() {
        return this._multiFactorAuthAge;
    }
}

module.exports = RequestContext;
<commit_msg>FT: Add PutBucketWebsite to RequestContext map<commit_after>'use strict'; // eslint-disable-line strict

const parseIp = require('../ipCheck').parseIp;
// http://docs.aws.amazon.com/IAM/latest/UserGuide/list_s3.html
// For MPU actions:
// http://docs.aws.amazon.com/AmazonS3/latest/dev/mpuAndPermissions.html
// For bucket head and object head:
// http://docs.aws.amazon.com/AmazonS3/latest/dev/
// using-with-s3-actions.html
const _actionMap = {
    bucketDelete: 's3:DeleteBucket',
    bucketGet: 's3:ListBucket',
    bucketGetACL: 's3:GetBucketAcl',
    bucketGetVersioning: 's3:GetBucketVersioning',
    bucketHead: 's3:ListBucket',
    bucketPut: 's3:CreateBucket',
    bucketPutACL: 's3:PutBucketAcl',
    bucketPutVersioning: 's3:PutBucketVersioning',
    bucketPutWebsite: 's3:PutBucketWebsite',
    completeMultipartUpload: 's3:PutObject',
    initiateMultipartUpload: 's3:PutObject',
    listMultipartUploads: 's3:ListBucketMultipartUploads',
    listParts: 's3:ListMultipartUploadParts',
    multipartDelete: 's3:AbortMultipartUpload',
    objectDelete: 's3:DeleteObject',
    objectGet: 's3:GetObject',
    objectGetACL: 's3:GetObjectAcl',
    objectHead: 's3:GetObject',
    objectPut: 's3:PutObject',
    objectPutACL: 's3:PutObjectAcl',
    objectPutPart: 's3:PutObject',
    serviceGet: 's3:ListAllMyBuckets',
};

const _actionMapIAM = {
    attachGroupPolicy: 'iam:AttachGroupPolicy',
    attachUserPolicy: 'iam:AttachUserPolicy',
    createAccessKey: 'iam:CreateAccessKey',
    createGroup: 'iam:CreateGroup',
    createPolicy: 'iam:CreatePolicy',
    createPolicyVersion: 'iam:CreatePolicyVersion',
    createUser: 'iam:CreateUser',
    deleteAccessKey: 'iam:DeleteAccessKey',
    deleteGroup: 'iam:DeleteGroup',
    deleteGroupPolicy: 'iam:DeleteGroupPolicy',
    deletePolicy: 'iam:DeletePolicy',
    deletePolicyVersion: 'iam:DeletePolicyVersion',
    deleteUser: 'iam:DeleteUser',
    detachGroupPolicy: 'iam:DetachGroupPolicy',
    detachUserPolicy: 'iam:DetachUserPolicy',
    getGroup: 'iam:GetGroup',
    getGroupPolicy: 'iam:GetGroupPolicy',
    getPolicy: 'iam:GetPolicy',
    getPolicyVersion: 'iam:GetPolicyVersion',
    getUser: 'iam:GetUser',
    listAccessKeys: 'iam:ListAccessKeys',
    listGroupPolicies: 'iam:ListGroupPolicies',
    listGroups: 'iam:ListGroups',
    listGroupsForUser: 'iam:ListGroupsForUser',
    listPolicies: 'iam:ListPolicies',
    listPolicyVersions: 'iam:ListPolicyVersions',
    listUsers: 'iam:ListUsers',
    putGroupPolicy: 'iam:PutGroupPolicy',
    removeUserFromGroup: 'iam:RemoveUserFromGroup',
};

function _findAction(service, method) {
    if (service === 's3') {
        return _actionMap[method];
    }
    if (service === 'iam') {
        return _actionMapIAM[method];
    }
    if (service === 'utapi') {
        // currently only method is ListMetrics
        return `utapi:${method}`;
    }
    return undefined;
}

function _buildArn(service, generalResource, specificResource, requesterInfo) {
      // arn:partition:service:region:account-id:resourcetype/resource
    if (service === 's3') {
        // arn:aws:s3:::bucket/object
        // General resource is bucketName
        if (generalResource && specificResource) {
            return `arn:aws:s3:::${generalResource}/${specificResource}`;
        } else if (generalResource) {
            return `arn:aws:s3:::${generalResource}`;
        }
        return 'arn:aws:s3:::';
    }
    if (service === 'iam') {
        // arn:aws:iam::<account-id>:<resource-type><resource>
        if (specificResource) {
            return `arn:aws:iam::${requesterInfo.accountid}:` +
                `${generalResource}${specificResource}`;
        }
        return `arn:aws:iam::${requesterInfo.accountid}:${generalResource}`;
    }
    if (service === 'utapi') {
        // arn:scality:utapi:::resourcetype/resource
        // (possible resource types are buckets, accounts or users)
        if (specificResource) {
            return `arn:scality:utapi:::${generalResource}/${specificResource}`;
        }
        return `arn:scality:utapi:::${generalResource}/`;
    }
    return undefined;
}

/**
 * Class containing RequestContext for policy auth check
 * @param {object} headers - request headers
 * @param {query} query - request query
 * @param {string} generalResource - bucket name from request if any if from s3
 * or accounts, buckets or users from utapi
 * @param {string} specificResource - object name from request if any if from s3
 * or bucketname from utapi if from utapi
 * @param {string} requesterIp - ip of requester
 * @param {boolean} sslEnabled - whether request was https
 * @param {string} apiMethod - type of request
 * @param {string} awsService - service receiving request
 * @param {string} locationConstraint - location constraint
 * for put bucket operation
 * @param {object} requesterInfo - info about entity making request
 * @param {string} signatureVersion - auth signature type used
 * @param {string} authType - type of authentication used
 * @param {number} signatureAge - age of signature in milliseconds
 * @return {RequestContext} a RequestContext instance
 */

class RequestContext {
    constructor(headers, query, generalResource, specificResource,
        requesterIp, sslEnabled, apiMethod,
        awsService, locationConstraint, requesterInfo,
        signatureVersion, authType, signatureAge) {
        this._headers = headers;
        this._query = query;
        this._requesterIp = requesterIp;
        this._sslEnabled = sslEnabled;
        this._apiMethod = apiMethod;
        this._awsService = awsService;
        this._generalResource = generalResource;
        this._specificResource = specificResource;
        this._locationConstraint = locationConstraint;
        // Not implemented
        this._multiFactorAuthPresent = null;
        // Not implemented
        this._multiFactorAuthAge = null;
        // Not implemented
        this._tokenIssueTime = null;

        // Remainder not set when originally instantiated
        // (unless if instantiated from deSerialize)
        this._requesterInfo = requesterInfo;
        // See http://docs.aws.amazon.com/AmazonS3/latest/
        // API/bucket-policy-s3-sigv4-conditions.html
        this._signatureVersion = signatureVersion;
        this._authType = authType;
        this._signatureAge = signatureAge;

        return this;
    }

    /**
    * Serialize the object
    * @return {string} - stringified object
    */
    serialize() {
        const requestInfo = {
            apiMethod: this._apiMethod,
            headers: this._headers,
            query: this._query,
            requersterInfo: this._requesterInfo,
            requesterIp: this._requesterIp,
            sslEnabled: this._sslEnabled,
            awsService: this._awsService,
            generalResource: this._generalResource,
            specificResource: this._specificResource,
            multiFactorAuthPresent: this._multiFactorAuthPresent,
            multiFactorAuthAge: this._multiFactorAuthAge,
            signatureVersion: this._signatureVersion,
            authType: this._authType,
            signatureAge: this._signatureAge,
            locationConstraint: this._locationConstraint,
            tokenIssueTime: this._tokenIssueTime,
        };
        return JSON.stringify(requestInfo);
    }

    /**
     * deSerialize the JSON string
     * @param {string} stringRequest - the stringified requestContext
     * @return {object} - parsed string
     */
    static deSerialize(stringRequest) {
        let obj;
        try {
            obj = JSON.parse(stringRequest);
        } catch (err) {
            return new Error(err);
        }
        return new RequestContext(obj.headers, obj.query, obj.generalResource,
            obj.specificResource, obj.requesterIp, obj.sslEnabled,
            obj.apiMethod, obj.awsService, obj.locationConstraint,
            obj.requesterInfo, obj.signatureVersion,
            obj.authType, obj.signatureAge);
    }

    /**
    * Get the request action
    * @return {string} action
    */
    getAction() {
        if (this._foundAction) {
            return this._foundAction;
        }
        this._foundAction = _findAction(this._awsService, this._apiMethod);
        return this._foundAction;
    }

    /**
    * Get the resource impacted by the request
    * @return {string} arn for the resource
    */
    getResource() {
        if (this._foundResource) {
            return this._foundResource;
        }
        this._foundResource =
            _buildArn(this._awsService, this._generalResource,
                this._specificResource, this._requesterInfo);
        return this._foundResource;
    }

    /**
     * Set headers
     * @param {object} headers - request headers
     * @return {RequestContext} - RequestContext instance
     */
    setHeaders(headers) {
        this._headers = headers;
        return this;
    }
    /**
     * Get headers
     * @return {object} request headers
     */
    getHeaders() {
        return this._headers;
    }

    /**
     * Set query
     * @param {object} query - request query
     * @return {RequestContext} - RequestContext instance
     */
    setQuery(query) {
        this._query = query;
        return this;
    }
    /**
     * Get query
     * @return {object} request query
     */
    getQuery() {
        return this._query;
    }

    /**
     * Set requesterInfo
     * @param {object} requesterInfo - info about entity making request
     * @return {RequestContext} - RequestContext instance
     */
    setRequesterInfo(requesterInfo) {
        this._requesterInfo = requesterInfo;
        return this;
    }
    /**
     * Get requesterInfo
     * @return {object} requesterInfo
     */
    getRequesterInfo() {
        return this._requesterInfo;
    }

    /**
     * Set requesterIp
     * @param {string} requesterIp - ip address of requester
     * @return {RequestContext} - RequestContext instance
     */
    setRequesterIp(requesterIp) {
        this._requesterIp = requesterIp;
        return this;
    }
    /**
     * Get requesterIp
     * @return {object} requesterIp - parsed requesterIp
     */
    getRequesterIp() {
        return parseIp(this._requesterIp);
    }

    /**
     * Set sslEnabled
     * @param {boolean} sslEnabled - true if https used
     * @return {RequestContext} - RequestContext instance
     */
    setSslEnabled(sslEnabled) {
        this._sslEnabled = sslEnabled;
        return this;
    }

    /**
     * Get sslEnabled
     * @return {boolean} true if sslEnabled, false if not
     */
    getSslEnabled() {
        return !!this._sslEnabled;
    }

    /**
     * Set signatureVersion
     * @param {string} signatureVersion - "AWS" identifies Signature Version 2
     * and "AWS4-HMAC-SHA256" identifies Signature Version 4
     * @return {RequestContext} - RequestContext instance
     */
    setSignatureVersion(signatureVersion) {
        this._signatureVersion = signatureVersion;
        return this;
    }

    /**
     * Get signatureVersion
     *
     * @return {string} authentication signature version
     * "AWS" identifies Signature Version 2 and
     * "AWS4-HMAC-SHA256" identifies Signature Version 4
     */
    getSignatureVersion() {
        return this._signatureVersion;
    }

    /**
     * Set authType
     * @param {string} authType - REST-HEADER, REST-QUERY-STRING or POST
     * @return {RequestContext} - RequestContext instance
     */
    setAuthType(authType) {
        this._authType = authType;
        return this;
    }

    /**
     * Get authType
     * @return {string} authentication type:
     * REST-HEADER, REST-QUERY-STRING or POST
     */
    getAuthType() {
        return this._authType;
    }

    /**
     * Set signatureAge
     * @param {number} signatureAge -- age of signature in milliseconds
     * Note that for v2 query auth this will be undefined (since these
     * requests are pre-signed and only come with an expires time so
     * do not know age)
     * @return {RequestContext} - RequestContext instance
     */
    setSignatureAge(signatureAge) {
        this._signatureAge = signatureAge;
        return this;
    }
    /**
     * Get signatureAge
     * @return {number} age of signature in milliseconds
     * Note that for v2 query auth this will be undefined (since these
     * requests are pre-signed and only come with an expires time so
     * do not know age)
     */
    getSignatureAge() {
        return this._signatureAge;
    }

    /**
     * Set locationConstraint
     * @param {string} locationConstraint - bucket region constraint
     * @return {RequestContext} - RequestContext instance
     */
    setLocationConstraint(locationConstraint) {
        this._locationConstraint = locationConstraint;
        return this;
    }
    /**
     * Get locationConstraint
     * @return {string} location constraint of put bucket request
     */
    getLocationConstraint() {
        return this._locationConstraint;
    }

    /**
     * Set awsService
     * @param {string} awsService receiving request
     * @return {RequestContext} - RequestContext instance
     */
    setAwsService(awsService) {
        this._awsService = awsService;
        return this;
    }
    /**
     * Get awsService
     * @return {string} awsService receiving request
     */
    getAwsService() {
        return this._awsService;
    }

    /**
     * Set tokenIssueTime
     * @param {string} tokenIssueTime - Date/time that
     * temporary security credentials were issued
     * Only present in requests that are signed using
     * temporary security credentials.
     * @return {RequestContext} - RequestContext instance
     */
    setTokenIssueTime(tokenIssueTime) {
        this._tokenIssueTime = tokenIssueTime;
        return this;
    }
    /**
     * Get tokenIssueTime
     * @return {string} tokenIssueTime
     */
    getTokenIssueTime() {
        return this._tokenIssueTime;
    }

    /**
     * Set multiFactorAuthPresent
     * @param {boolean} multiFactorAuthPresent - sets out whether MFA used
     * for request
     * @return {RequestContext} - RequestContext instance
     */
    setMultiFactorAuthPresent(multiFactorAuthPresent) {
        this._multiFactorAuthPresent = multiFactorAuthPresent;
        return this;
    }
    /**
     * Get multiFactorAuthPresent
     * @return {boolean} multiFactorAuthPresent
     */
    getMultiFactorAuthPresent() {
        return this._multiFactorAuthPresent;
    }

    /**
     * Set multiFactorAuthAge
     * @param {number} multiFactorAuthAge - seconds since
     * MFA credentials were issued
     * @return {RequestContext} - RequestContext instance
     */
    setMultiFactorAuthAge(multiFactorAuthAge) {
        this._multiFactorAuthAge = multiFactorAuthAge;
        return this;
    }
    /**
     * Get multiFactorAuthAge
     * @return {number} multiFactorAuthAge - seconds since
     *  MFA credentials were issued
     */
    getMultiFactorAuthAge() {
        return this._multiFactorAuthAge;
    }
}

module.exports = RequestContext;
<|endoftext|>


<commit_before>import process from '../direct';
import { Events } from '../../constants';

describe('Processors - Direct', function () {
    it('should only work with update/remove', function () {
        let observableCollection = {
            update(docId, doc) {
                assert.equal(doc._id, 'YYY');
                assert.equal(docId, 'YYY');
            },
            remove(docId) {
                assert.equal(docId, 'YYY');
            }
        };

        process(observableCollection, Events.UPDATE, {
            _id: 'YYY',
        });
        process(observableCollection, Events.REMOVE, {
            _id: 'YYY',
        });
    })
});<commit_msg>fix test for direct<commit_after>import process from '../direct';
import { Events } from '../../constants';

describe('Processors - Direct', function () {
    it('should only work with update/remove', function () {
        let observableCollection = {
            change(docId, doc) {
                assert.equal(doc._id, 'YYY');
                assert.equal(docId, 'YYY');
            },
            remove(docId) {
                assert.equal(docId, 'YYY');
            }
        };

        process(observableCollection, Events.UPDATE, {
            _id: 'YYY',
        });
        process(observableCollection, Events.REMOVE, {
            _id: 'YYY',
        });
    })
});<|endoftext|>


<commit_before>
Tower.View.AssetHelper = {
  javascripts: function() {
    var options, path, paths, sources, _i, _len;
    sources = _.args(arguments);
    options = _.extractOptions(sources);
    options.namespace = "javascripts";
    options.extension = "js";
    paths = _extractAssetPaths(sources, options);
    for (_i = 0, _len = paths.length; _i < _len; _i++) {
      path = paths[_i];
      javascriptTag(path);
    }
    return null;
  },
  javascript: function() {
    return javascripts.apply(this, arguments);
  },
  stylesheets: function() {
    var options, path, paths, sources, _i, _len;
    sources = _.args(arguments);
    options = _.extractOptions(sources);
    options.namespace = "stylesheets";
    options.extension = "css";
    paths = _extractAssetPaths(sources, options);
    for (_i = 0, _len = paths.length; _i < _len; _i++) {
      path = paths[_i];
      stylesheetTag(path);
    }
    return null;
  },
  stylesheet: function() {
    return stylesheets.apply(this, arguments);
  },
  stylesheetTag: function(source) {
    return link({
      rel: 'stylesheet',
      href: source
    });
  },
  javascriptTag: function(source) {
    return script({
      src: source
    });
  },
  _extractAssetPaths: function(sources, options) {
    var extension, manifest, namespace, path, paths, result, source, _i, _j, _k, _len, _len1, _len2;
    if (options == null) {
      options = {};
    }
    namespace = options.namespace;
    extension = options.extension;
    result = [];
    if (Tower.env === "production") {
      manifest = Tower.assetManifest;
      for (_i = 0, _len = sources.length; _i < _len; _i++) {
        source = sources[_i];
        if (!source.match(/^(http|\/{2})/)) {
          source = "" + source + "." + extension;
          if (manifest[source]) {
            source = manifest[source];
          }
          source = "/assets/" + source;
          if (Tower.assetHost) {
            source = "" + Tower.assetHost + source;
          }
        }
        result.push(source);
      }
    } else {
      for (_j = 0, _len1 = sources.length; _j < _len1; _j++) {
        source = sources[_j];
        if (!!source.match(/^(http|\/{2})/)) {
          result.push(source);
        } else {
          paths = Tower.config.assets[namespace][source];
          if (paths) {
            for (_k = 0, _len2 = paths.length; _k < _len2; _k++) {
              path = paths[_k];
              result.push("/" + namespace + path + "." + extension);
            }
          }
        }
      }
    }
    return result;
  }
};

module.exports = Tower.View.AssetHelper;
<commit_msg>Make the paths in tune with file structure uploaded to s3 with cake assets:upload:s3<commit_after>Tower.View.AssetHelper = {
  javascripts: function() {
    var options, path, paths, sources, _i, _len;
    sources = _.args(arguments);
    options = _.extractOptions(sources);
    options.namespace = "javascripts";
    options.extension = "js";
    paths = _extractAssetPaths(sources, options);
    for (_i = 0, _len = paths.length; _i < _len; _i++) {
      path = paths[_i];
      javascriptTag(path);
    }
    return null;
  },
  javascript: function() {
    return javascripts.apply(this, arguments);
  },
  stylesheets: function() {
    var options, path, paths, sources, _i, _len;
    sources = _.args(arguments);
    options = _.extractOptions(sources);
    options.namespace = "stylesheets";
    options.extension = "css";
    paths = _extractAssetPaths(sources, options);
    for (_i = 0, _len = paths.length; _i < _len; _i++) {
      path = paths[_i];
      stylesheetTag(path);
    }
    return null;
  },
  stylesheet: function() {
    return stylesheets.apply(this, arguments);
  },
  stylesheetTag: function(source) {
    return link({
      rel: 'stylesheet',
      href: source
    });
  },
  javascriptTag: function(source) {
    return script({
      src: source
    });
  },
  _extractAssetPaths: function(sources, options) {
    var extension, manifest, namespace, path, paths, result, source, _i, _j, _k, _len, _len1, _len2;
    if (options == null) {
      options = {};
    }
    namespace = options.namespace;
    extension = options.extension;
    result = [];
    if (Tower.env === "production") {
      manifest = Tower.assetManifest;
      for (_i = 0, _len = sources.length; _i < _len; _i++) {
        source = sources[_i];
        if (!source.match(/^(http|\/{2})/)) {
          source = "" + source + "." + extension;
          if (manifest[source]) {
            source = manifest[source];
          }
          if (Tower.assetHost) {
            if (Tower.assetHost.match(/^https?:\/{2}s3\./)) {
          		switch(source.match(/[^\.]+$/)[0]) {
		          	case 'css':
		          		source = "/stylesheets/" + source;
		          		break;

		          	case 'js':
		          		source = "/javascripts/" + source;
		          		break;

		          	case 'jpg':
		          	case 'gif':
		          	case 'png':
		          		source = "/images/" + source;
		          		break;

		          	default:
		          		source = "/assets/" + source;
		          		break;
		          }
          	}
            source = "" + Tower.assetHost + source;
          }
          else {
          	source = "/assets/" + source;
          }
        }
        result.push(source);
      }
    } else {
      for (_j = 0, _len1 = sources.length; _j < _len1; _j++) {
        source = sources[_j];
        if (!!source.match(/^(http|\/{2})/)) {
          result.push(source);
        } else {
          paths = Tower.config.assets[namespace][source];
          if (paths) {
            for (_k = 0, _len2 = paths.length; _k < _len2; _k++) {
              path = paths[_k];
              result.push("/" + namespace + path + "." + extension);
            }
          }
        }
      }
    }
    return result;
  }
};

module.exports = Tower.View.AssetHelper;
<|endoftext|>


<commit_before>describe('Game', function() {
  var game;

  beforeEach(function() {
    game = new Game(5);
  });

  it('has a board', function() {
    expect(game.board instanceof Board).toEqual(true);
  });

  describe('#get_board', function() {
    it('returns a board', function() {
      expect(game.get_board() instanceof Board).toEqual(true);
    });
  });

  describe('#start', function() {
    it('sets two random tiles', function() {
      spyOn(game.board, 'set_random_empty_tile');
      game.start();
      expect(game.board.set_random_empty_tile.calls.count()).toEqual(2);
    });
  });

  describe('#has_possible_move', function() {
    it('checks whether there are any possible moves', function() {
      spyOn(game.board, 'has_possible_move');
      game.has_possible_move();
      expect(game.board.has_possible_move).toHaveBeenCalled();
    });
  });

  describe('#move', function() {
    it('moves the board left', function() {
      spyOn(game, 'left').and.callThrough();
      game.move('left');
      expect(game.left).toHaveBeenCalled();
    });

    describe('when the board has an empty tile', function() {
      describe('when moving the board results in a change', function() {
        it('spawns a random tile', function() {
          game.board.board = [
            [0, 2, 0, 0, 0],
            [0, 2, 0, 0, 0],
            [0, 2, 0, 0, 0],
            [0, 2, 0, 0, 0],
            [0, 2, 0, 0, 0]
          ];

          spyOn(game.board, 'set_random_empty_tile').and.callThrough();
          game.move('left');
          expect(game.board.set_random_empty_tile).toHaveBeenCalled();
        });
      });

      it('returns true', function() {
        expect(game.move('left')).toEqual(true);
      });
    });

    describe('when the board has no empty tiles', function() {
      it('returns false', function() {
        // this board has no left-mergeable tiles
        game.board.board = [
          [2, 4, 8, 16, 32],
          [2, 4, 8, 16, 32],
          [2, 4, 8, 16, 32],
          [2, 4, 8, 16, 32],
          [2, 4, 8, 16, 32]
        ];

        expect(game.move('left')).toEqual(false);
      });
    });
  });
});
<commit_msg>Clarify test wording.<commit_after>describe('Game', function() {
  var game;

  beforeEach(function() {
    game = new Game(5);
  });

  it('has a board', function() {
    expect(game.board instanceof Board).toEqual(true);
  });

  describe('#get_board', function() {
    it('returns a board', function() {
      expect(game.get_board() instanceof Board).toEqual(true);
    });
  });

  describe('#start', function() {
    it('sets two random tiles', function() {
      spyOn(game.board, 'set_random_empty_tile');
      game.start();
      expect(game.board.set_random_empty_tile.calls.count()).toEqual(2);
    });
  });

  describe('#has_possible_move', function() {
    it('checks whether there are any possible moves', function() {
      spyOn(game.board, 'has_possible_move');
      game.has_possible_move();
      expect(game.board.has_possible_move).toHaveBeenCalled();
    });
  });

  describe('#move', function() {
    it('moves the board in a direction', function() {
      spyOn(game, 'left').and.callThrough();
      game.move('left');
      expect(game.left).toHaveBeenCalled();
    });

    describe('when the board has an empty tile', function() {
      describe('when moving the board results in a change', function() {
        it('spawns a random tile', function() {
          game.board.board = [
            [0, 2, 0, 0, 0],
            [0, 2, 0, 0, 0],
            [0, 2, 0, 0, 0],
            [0, 2, 0, 0, 0],
            [0, 2, 0, 0, 0]
          ];

          spyOn(game.board, 'set_random_empty_tile').and.callThrough();
          game.move('left');
          expect(game.board.set_random_empty_tile).toHaveBeenCalled();
        });
      });

      it('returns true', function() {
        expect(game.move('left')).toEqual(true);
      });
    });

    describe('when the board has no empty tiles', function() {
      it('returns false', function() {
        // this board has no left-mergeable tiles
        game.board.board = [
          [2, 4, 8, 16, 32],
          [2, 4, 8, 16, 32],
          [2, 4, 8, 16, 32],
          [2, 4, 8, 16, 32],
          [2, 4, 8, 16, 32]
        ];

        expect(game.move('left')).toEqual(false);
      });
    });
  });
});
<|endoftext|>


<commit_before>/*
 * Consume: http://$FQDN:$PORT/um/lib/wes
 *
 * Wait events from backend using "long pooling".
 * The request is pending until something is there.
 * Thus:
 * >
 * > App.um.wes(status)
 * >
 * will not response until *next* event from backend
 *
 *
 * @status is current user status from backend:
 * App.User.id: 'onlidev@127.0.0.1 BcORk11VGsInZtzrLHD0LtYy'
 *   user status:^^^^   |         |
 *      user auth id:^^^|         |
 *            IP address:^^^^^^^^^|
 *                 `req.sessionID`:^^^^^^^^^^^^^^^^^^^^^^^^
 *
 * used in e.g. Chat to make this user bold
 *
 * If there is no pending `res` in session,
 * then user status is set `offline` ('appbar-user-offl')
 *
 * backend errors: w.res.statusCode = 501// "Not Implemented" + JSON
 *
 **/

App.um.wes = (
function create_backend_wait_events(conn){
var defaults
    /* channel#2: receive events from backend, send user status */
    conn.suspendEvents(false)// `this` fires own signal in callback()
    conn.url = App.backendURL + '/um/lib/wes'
    conn.timeout = App.cfg.extjs.wes.timeout || (1 << 22)// ~ hour
    conn.defer = null
    defaults = {
    // `wes` has only one polling `res` per `req.session`,
    // but to send new status second `req` is firing
        autoAbort: false,
        callback: backend_events
    }
    console.log('init: ' + App.User.id)
    req('onli')// setup user status by 'appbar-user-onli'

    return req// return function to act manually e.g: `App.um.wes(status)`

    function req(opts){
    var ev = 'initwes@UI'

        Ext.globalEvents.fireEventArgs(ev, [ ev ])

        if(!opts){
            opts = { }
        } else if('string' == typeof opts){// change status
            opts = { params: opts }
        }
        if(!opts.params){// if empty send current user status
            opts.params = App.User.id.slice(0, 4)
        }

        return conn.request(Ext.applyIf(opts, defaults))
    }

    function backend_events(o, success, res){
    var data, i

        if(success){
            // reset error state if any
            if(App.User.internalId){// tmp store for status in case of connection errors
                App.User.id = App.User.internalId// restore past status
                App.User.internalId = ''
            }
            if(conn.defer){
                clearTimeout(conn.defer)
                conn.defer = 0
            }
            // data processing
            if(!res.responseText) return req()// nothing to do
            try {
                data = JSON.parse(res.responseText)
            } catch(ex){
                success = false
                console.error('JSON App.um.wes:')
                console.error(res)
                Ext.Msg.show({
                   title: l10n.errun_title + ' JSON App.um.wes',
                   buttons: Ext.Msg.OK,
                   icon: Ext.Msg.ERROR,
                   msg:
('data: ' + (res.responseText || 'empty')).slice(0, 16).replace(/</g, '<')
+'
'+ l10n.errun_stack + '' + ex.stack.replace(/</g, '<')
                })
            }
            if(!data || !data.length) return req()// nothing to do
            // handle own events
            i = data.length - 1
            do {// scan from bottom up (our event are likely to be last in list)
                switch(data[i].ev){
                // broadcasts: 'login@um' 'initwes@um' 'usts@um' ....
                default: break
                // == private events ==
                case 'Usts@um':
                // assign backend's ID instead of autogenerated User Model's ID
                    o = data[i].json
                    if(o && App.User.id != o){
                        App.User.id = o// UI event is in `um.controller.Login`
                        App.User.internalId && (App.User.internalId = '')
                    }
                    if('initwes@um' != data[0].ev &&// this is 1st event
                     !(data[2] && 'login@um' == data[2].ev)){// this is 3d one
                        o = null// do not setup twice if not init
                    }
                }
            } while(i--)
            if(o) req()// setup wes for next events, if not manual status

            return Ext.globalEvents.fireEventArgs(// broadcast !own event
                'wes4UI',
                [ success, data ]
            )
        }

        if(res.timedout) return req()// just restart polling
        if(-1 == res.status){// abort
            console.warn('wes: abort detected')
            return req()// try to restart polling
        }

        console.error('wes:', res)

        if(!App.User.internalId){
            App.User.internalId = App.User.id// save current status
            // mark as offline in case of permanent error
            App.User.id = 'offl' + App.User.id.slice(4)
        }
        conn.defer = Ext.defer(// retry a bit later
            req,
            App.cfg.extjs.wes.defer || (1 << 17)// ~ two minutes
        )
        Ext.globalEvents.fireEventArgs(
            'wes4UI',
            [ success, res.statusText || 'Disconnect']
        )

        return undefined
    }
})(Ext.create(App.backend.Connection))
<commit_msg>App.um.wes: fire events only if they were not handeled here<commit_after>/*
 * Consume: http://$FQDN:$PORT/um/lib/wes
 *
 * Wait events from backend using "long pooling".
 * The request is pending until something is there.
 * Thus:
 * >
 * > App.um.wes(status)
 * >
 * will not response until *next* event from backend
 *
 *
 * @status is current user status from backend:
 * App.User.id: 'onlidev@127.0.0.1 BcORk11VGsInZtzrLHD0LtYy'
 *   user status:^^^^   |         |
 *      user auth id:^^^|         |
 *            IP address:^^^^^^^^^|
 *                 `req.sessionID`:^^^^^^^^^^^^^^^^^^^^^^^^
 *
 * used in e.g. Chat to make this user bold
 *
 * If there is no pending `res` in session,
 * then user status is set `offline` ('appbar-user-offl')
 *
 * backend errors: w.res.statusCode = 501// "Not Implemented" + JSON
 *
 **/

App.um.wes = (
function create_backend_wait_events(conn){
var defaults
    /* channel#2: receive events from backend, send user status */
    conn.suspendEvents(false)// `this` fires own signal in callback()
    conn.url = App.backendURL + '/um/lib/wes'
    conn.timeout = App.cfg.extjs.wes.timeout || (1 << 22)// ~ hour
    conn.defer = null
    defaults = {
    // `wes` has only one polling `res` per `req.session`,
    // but to send new status second `req` is firing
        autoAbort: false,
        callback: backend_events
    }
    console.log('init: ' + App.User.id)
    req('onli')// setup user status by 'appbar-user-onli'

    return req// return function to act manually e.g: `App.um.wes(status)`

    function req(opts){
    var ev = 'initwes@UI'

        Ext.globalEvents.fireEventArgs(ev, [ ev ])

        if(!opts){
            opts = { }
        } else if('string' == typeof opts){// change status
            opts = { params: opts }
        }
        if(!opts.params){// if empty send current user status
            opts.params = App.User.id.slice(0, 4)
        }

        return conn.request(Ext.applyIf(opts, defaults))
    }

    function backend_events(o, success, res){
    var data, i, done

        if(success){
            // reset error state if any
            if(App.User.internalId){// tmp store for status in case of connection errors
                App.User.id = App.User.internalId// restore past status
                App.User.internalId = ''
            }
            if(conn.defer){
                clearTimeout(conn.defer)
                conn.defer = 0
            }
            // data processing
            if(!res.responseText) return req()// nothing to do
            try {
                data = JSON.parse(res.responseText)
            } catch(ex){
                success = false
                console.error('JSON App.um.wes:')
                console.error(res)
                Ext.Msg.show({
                   title: l10n.errun_title + ' JSON App.um.wes',
                   buttons: Ext.Msg.OK,
                   icon: Ext.Msg.ERROR,
                   msg:
('data: ' + (res.responseText || 'empty')).slice(0, 16).replace(/</g, '<')
+'
'+ l10n.errun_stack + '' + ex.stack.replace(/</g, '<')
                })
            }
            if(!data || !data.length) return req()// nothing to do
            // handle own events
            i = data.length - 1
            done = 0
            do {// scan from bottom up (our event are likely to be last in list)
                switch(data[i].ev){
                // broadcasts: 'login@um' 'initwes@um' 'usts@um' ....
                default: break
                // == private events ==
                case 'Usts@um':
                // assign backend's ID instead of autogenerated User Model's ID
                    o = data[i].json
                    if(o && App.User.id != o){
                        App.User.id = o// UI event is in `um.controller.Login`
                        App.User.internalId && (App.User.internalId = '')
                    }
                    if('initwes@um' != data[0].ev &&// this is 1st event
                     !(data[2] && 'login@um' == data[2].ev)){// this is 3d one
                        o = null// do not setup twice if not init
                        done += 2
                    }
                    done++
                }
            } while(i--)
            if(o) req()// setup wes for next events, if not manual status
            // broadcast !own event if there are more events
            return (done == data.length) ? undefined : Ext.globalEvents.fireEventArgs(
                'wes4UI',
                [ success, data ]
            )
        }

        if(res.timedout) return req()// just restart polling
        if(-1 == res.status){// abort
            console.warn('wes: abort detected')
            return req()// try to restart polling
        }

        console.error('wes:', res)

        if(!App.User.internalId){
            App.User.internalId = App.User.id// save current status
            // mark as offline in case of permanent error
            App.User.id = 'offl' + App.User.id.slice(4)
        }
        conn.defer = Ext.defer(// retry a bit later
            req,
            App.cfg.extjs.wes.defer || (1 << 17)// ~ two minutes
        )
        Ext.globalEvents.fireEventArgs(
            'wes4UI',
            [ success, res.statusText || 'Disconnect']
        )

        return undefined
    }
})(Ext.create(App.backend.Connection))
<|endoftext|>


<commit_before>(function ($) {

    $.entwine('ss', function ($) {

        $('.ss-seo-editor .ss-gridfield-item input, .ss-seo-editor .ss-gridfield-item textarea').entwine({

            onchange: function () {

                // kill the popup for form changes
                window.onbeforeunload = null;

                var $this = $(this);
                var id = $this.closest('tr').attr('data-id');
                var url = $this.closest('.ss-gridfield').attr('data-url') + "/update/" + id;
                var data = $this.attr('name') + '=' + $(this).val();

                statusMessage('Saving changes', 'good');

                $.post(
                    url,
                    data,
                    function (data, textStatus) {
                        statusMessage(data.message, data.type);

                        $this.closest('td').removeClass();
                        if (data.errors.length) {
                            $this.closest('td').addClass('seo-editor-error');
                            data.errors.forEach(function(error) {
                                $this.closest('td').addClass(error)
                            });
                        } else {
                            $this.closest('td').addClass('seo-editor-valid');
                        }

                    },
                    'json'
                );
            }
        });

    });

}(jQuery));<commit_msg>Update seo-editor.js<commit_after>(function ($) {

    $.entwine('ss', function ($) {

        $('.ss-seo-editor .ss-gridfield-item input, .ss-seo-editor .ss-gridfield-item textarea').entwine({

            onchange: function () {

                // kill the popup for form changes
                window.onbeforeunload = null;

                var $this = $(this);
                var id = $this.closest('tr').attr('data-id');
                var preURL = $this.closest('.ss-gridfield').attr('data-url');
                var url;

                if(preURL.indexOf('?') != -1)
                    url = preURL.substr(0, preURL.indexOf('?')) + "/update/" + id + preURL.substr(preURL.indexOf('?'));
                else
                    url = preURL + "/update/" + id;

                var data = $this.attr('name') + '=' + $(this).val();

                statusMessage('Saving changes', 'good');

                $.post(
                    url,
                    data,
                    function (data, textStatus) {
                        statusMessage(data.message, data.type);

                        $this.closest('td').removeClass();
                        if (data.errors.length) {
                            $this.closest('td').addClass('seo-editor-error');
                            data.errors.forEach(function(error) {
                                $this.closest('td').addClass(error)
                            });
                        } else {
                            $this.closest('td').addClass('seo-editor-valid');
                        }

                    },
                    'json'
                );
            }
        });

    });

}(jQuery));
<|endoftext|>


<commit_before>/*
stat effects from a fraymotif are temporary. wear off after battle.
so, players, player snapshots AND gameEntities will have to have an array of applied fraymotifs.
and their getPower, getHP etc stats must use these.
at start AND end of battle (can't be too careful), wipe applied fraymotifs
*/
function Fraymotif(aspects, name,tier){
    this.aspects = aspects; //expect to be an array
    this.name = name;
    this.tier = tier;
    this.used = false; //when start fight, set to false. set to true when used. once per fight
	this.effects = [];  //each effect is a target, a revive, a statName
	this.baseDamage = 50 * this.tier;
	if(this.tier == 3) this.baseDamage = 1000;

    this.toString  = function(){
      return this.name;
    }

	this.addEffectsForPlayers = function(players){
		for(var i = 0; i<players.length; i++){
			this.addEffectsForPlayer(player);
		}
	}

    //effects are frozen at creation, basically.  if this fraymotif is created by a Bard of Breath in a session with a Prince of Time,
    //who then dies, and then a combo session results in an Heir of Time being able to use it with the Bard of Breath, then it'll still have the prince effect.
    this.addEffectsForPlayer = function(player){
		//each type of class does different possibilities, with a random Associated STat (from Aspect) from their list.
		//determine who the target is FIRST, if it's you or allies, choose a postitive stat, if it's enmy, choose a negative stat.
		//again, classes determine who target is, whether you do buff or damage (can do both if it's a multi aspect thing, but at this level, it's either or)

		//IMPORTANT, HOW SHOULD REVIVE BE TREATED???  on a per aspect level? time, life, hope all have revive effects?

		/*
			Knights can buff self or damage an enemy.
			Seers can buff all allies or debuff all enemies.
			Bards can damage all enemies or buff all allies.
			Heirs can buff self, a LOT.
			Maids can buff allies or damage all enemies.
			Rogues can damage an enemy or buff all allies.
			Pages can buff all allies, a LOT.
			Thiefs can damage an enemy and buff self.
		*/
    }

	this.calculateDamage = function(users, enemies){
		console.log("TODO: calculate  damage by all statName values for all involved users - all involved enemies ")
		/*
			base damage * 1, 2, or 3 for each stat.

			for each stat, sum the values of the stat for allies, and subtract the values for the stat for the enemies.
			if final value < baseDamage, damage = baseDaamge.  if final > base < 2Base, damage = 2base;  if final > 2base, damage = 3base;
		*/
	}
}

//no global functions any more. bad pastJR.
function FraymotifCreator(session){
  this.session = session;

  this.getRandomBreathName = function(){
      var names = ["Gale", "Feather", "Breathless","Jetstream", "Hurricane", "Tornado"," Kansas", "Breath", "Breeze", "Twister", "Storm", "Wild", "Inhale", "Windy", "Skylark", "Fugue", "Pneumatic", "Wheeze", "Forward", "Vertical", "Whirlwind", "Jetstream"];
      return getRandomElementFromArray(names)
  }

  this.getRandomRageName = function(){
      var names = ["Rage", "Barbaric", "Impossible", "Juggalo","Horrorcore" ,"Madness", "Carnival", "Mirthful", "Screaming", "Berserk", "MoThErFuCkInG", "War", "Haze", "Murder", "Furioso", "Aggressive", "ATBasher", "Violent", "Unbound", "Purple", "Unholy", "Hateful", "Fearful", "Inconceivable", "Impossible"];
      return getRandomElementFromArray(names)
  }

  this.getRandomLifeName = function(){
      var names = ["Life" ,"Pastoral", "Green", "Relief", "Healing", "Plant", "Vitality", "Growing", "Garden", "Multiplying", "Rising", "Survival", "Phoenix", "Respawn", "Mangrit", "Animato", "Gaia", "Increasing", "Overgrowth", "Jungle", "Harvest", "Lazarus"];
      return getRandomElementFromArray(names)
  }

  this.getRandomHopeName = function(){
      var names = ["Hope","Fake", "Belief", "Bullshit", "Determination", "Burn", "Stubborn", "Religion", "Will", "Hero", "Undying", "Dream", "Sepulchritude", "Prophet", "Apocryphal"];
      return getRandomElementFromArray(names)
  }

  this.getRandomVoidName = function(){
      var randBonus = "" + getRandomElementFromArray(interests) +  ""
      var names = ["Void","Disappearification","Pumpkin", "Nothing", "Emptiness", "Invisible", "Dark", "Hole", "Solo", "Silent", "Alone", "Night", "Null", "[Censored]", "[???]", "Vacuous", "Abyss", "Noir", "Blank", "Tenebrous", "Antithesis", "404"];
      return getRandomElementFromArray(names)+ randBonus;
  }

  this.getRandomLightName = function(){
      var names = ["Lucky", "Light", "Knowledge", "Blinding", "Brilliant", "Break", "Blazing", "Glow", "Flare", "Gamble", "Omnifold", "Apollo", "Helios", "Scintillating", "Horseshoe", "Leggiero", "Star", "Kindle", "Gambit", "Blaze"];
      return getRandomElementFromArray(names)
  }

  this.getRandomMindName = function(){
      var names = ["Mind", "Modulation", "Shock", "Awe", "Coin", "Judgement", "Mind", "Decision", "Spark", "Path", "Trial", "Variations", "Thunder", "Logical", "Telekinetic", "Brainiac", "Hysteria", "Deciso", "Thesis", "Imagination",  "Psycho", "Control", "Execution", "Bolt"];
      return getRandomElementFromArray(names)
  }

  this.getRandomHeartName = function(){
      var names = ["Heart","Soul", "Jazz", "Blues", "Spirit", "Splintering", "Clone", "Self", "Havoc", "Noble", "Animus", "Astral", "Shatter", "Breakdown", "Ethereal", "Beat", "Pulchritude"];
      return getRandomElementFromArray(names)
  }

  this.getRandomBloodName = function(){
      var names = ["Blood", "Chain", "Flow", "Charge", "Awakening", "Ichorial", "Friendship", "Trusting", "Clotting", "Union", "Bleeding", "Team", "Transfusion", "Pulse", "Sanguine", "Positive", "Negative", "Cruor", "Vim", "Chorus", "Iron", "Ichorial", "Fever", "Heat"];
      return getRandomElementFromArray(names)
  }

  this.getRandomDoomName = function(){
      var names = ["Dark", "Broken", "Meteoric", "Diseased","Fate", "Doomed", "Inevitable", "Doom", "End", "Final", "Dead", "Ruin", "Rot", "Coffin", "Apocalypse", "Morendo", "Smorzando", "~Ath", "Armistyx", "Grave", "Corpse", "Ashen", "Reaper", "Diseased", "Armageddon", "Cursed"];
      return getRandomElementFromArray(names)
  }

  this.getRandomTimeName = function(){
      var names = ["Time","Paradox", "Moment", "Foregone", "Reset", "Endless", "Temporal", "Shenanigans", "Clock", "Tick-Tock", "Spinning", "Repeat", "Rhythm", "Redshift",  "Epoch", "Beatdown", "Slow", "Remix", "Clockwork", "Lock", "Eternal"];
      return getRandomElementFromArray(names)
  }

  this.getRandomSpaceName = function(){
      var names = ["Canon","Space","Frogs", "Location", "Spatial", "Universe", "Infinite", "Spiral", "Physics", "Star", "Galaxy", "Nuclear", "Atomic", "Nucleus", "Horizon", "Event", "CROAK", "Spatium", "Squiddle", "Engine", "Meteor", "Gravity", "Crush"];
      return getRandomElementFromArray(names)
  }

  this.getRandomNameForAspect = function(aspect){
    console.log(aspect);
    var ret = "";
    if(aspect == "Blood") ret = this.getRandomBloodName();
    if(aspect == "Mind") ret = this.getRandomMindName();
    if(aspect == "Rage") ret =  this.getRandomRageName();
    if(aspect == "Time") ret =  this.getRandomTimeName();
    if(aspect == "Void") ret =  this.getRandomVoidName();
    if(aspect == "Heart") ret =  this.getRandomHeartName();
    if(aspect == "Breath") ret =  this.getRandomBreathName();
    if(aspect == "Light") ret =  this.getRandomLightName();
    if(aspect == "Space") ret =  this.getRandomSpaceName();
    if(aspect == "Hope") ret =  this.getRandomHopeName();
    if(aspect == "Life") ret =  this.getRandomLifeName();
    if(aspect == "Doom") ret =  this.getRandomDoomName();
    return getFontColorFromAspect(aspect) + ret + ""
  }

  this.getRandomMusicWord = function(aspect){ //takes in an aspect for color
    var names = ["Fortississimo", "Leitmotif", "Liberetto", "Sarabande", "Serenade", "Anthem", "Crescendo", "Vivace", "Encore", "Vivante", "Allegretto", "Fugue", "Choir", "Nobilmente", "Hymn", "Eroico", "Chant", "Mysterioso", "Diminuendo", "Perdendo", "Staccato", "Allegro", "Caloroso", "Nocturne"];
    names = names.concat(["Cadenza", "Cadence", "Waltz", "Concerto", "Finale", "Requiem", "Coda", "Dirge", "Battaglia", "Leggiadro", "Capriccio", "Presto", "Largo", "Accelerando", "Polytempo", "Overture", "Reprise", "Orchestra"])

    var ret = getRandomElementFromArray(names);
    if(Math.seededRandom() > 0.5){
      return "" + ret.toLowerCase()+"";  //tacked onto existin word
    }else{
      return " " + ret; //extra word
    }
  }

  this.getFraymotifName = function(players, tier){
    var name = "";
    var indexOfMusic = players.length-1;  //used to be random now always at end.
    if(players.length == 1){
      indexOfMusic = getRandomInt(0,tier-1);
      for(var i = 0; i < tier; i++){
        var musicWord = "";
        if(i == indexOfMusic) musicWord = this.getRandomMusicWord(players[i].aspect);
        name += this.getRandomNameForAspect(players[0].aspect) + musicWord +" ";
      }
    }else{

      for(var i = 0; i<players.length; i++){
        var musicWord = "";
        if(i == indexOfMusic) musicWord = this.getRandomMusicWord(players[i].aspect);
        name += this.getRandomNameForAspect(players[i].aspect) + musicWord +  " ";
      }
    }
    return name;
  }

  //classes is between 0 and aspects.length. each aspect is paired with a class.
  //should there be no class to pair with, random effect based on aspect
  //otherwise, effect is based on both class and aspect
  this.makeFraymotif = function(players,tier){
    var name = this.getFraymotifName(players, tier);
	var aspects = [];
	for(var i = 0; i<players.length; i++){
		if(aspects.indexOf(players[i].aspect) == -1) aspects.push(players[i].aspect); //if multiple of the same aspect is included here, just ignore.
	}
    var f= new Fraymotif(aspects, name, tier);
	f.addEffectsForPlayers(players);
	return f;
  }
}

function FraymotifEffect(statName, target){
	this.statName = statName;  //RELATIONSHIP, hp AND LIFE are all treated differently.  (Life can only be cast on allies), hp is applied to currentHp rather than hp. otherwise same stuff as associatedStats.
	this.target = target; //self, allies or enemy or enemies
}<commit_msg>coding<commit_after>/*
stat effects from a fraymotif are temporary. wear off after battle.
so, players, player snapshots AND gameEntities will have to have an array of applied fraymotifs.
and their getPower, getHP etc stats must use these.
at start AND end of battle (can't be too careful), wipe applied fraymotifs
*/
function Fraymotif(aspects, name,tier){
    this.aspects = aspects; //expect to be an array
    this.name = name;
    this.tier = tier;
    this.used = false; //when start fight, set to false. set to true when used. once per fight
	this.effects = [];  //each effect is a target, a revive, a statName
	this.baseDamage = 50 * this.tier;
	if(this.tier == 3) this.baseDamage = 1000;

    this.toString  = function(){
      return this.name;
    }

	this.addEffectsForPlayers = function(players){
		for(var i = 0; i<players.length; i++){
			var effect = new FraymotifEffect();
			effect.setEffectForPlayer(players[i]);
			this.effects.push(effect);
		}
	}

	this.useFraymotif = function(users, enemies){
		console.log("TODO: calculate  damage by all statName values for all involved users - all involved enemies ")
		/*
			base damage * 1, 2, or 3 for each stat.

			for each stat, sum the values of the stat for allies, and subtract the values for the stat for the enemies.
			if final value < baseDamage, damage = baseDaamge.  if final > base < 2Base, damage = 2base;  if final > 2base, damage = 3base;
		*/
	}
}

//no global functions any more. bad pastJR.
function FraymotifCreator(session){
  this.session = session;

  this.getRandomBreathName = function(){
      var names = ["Gale", "Feather", "Breathless","Jetstream", "Hurricane", "Tornado"," Kansas", "Breath", "Breeze", "Twister", "Storm", "Wild", "Inhale", "Windy", "Skylark", "Fugue", "Pneumatic", "Wheeze", "Forward", "Vertical", "Whirlwind", "Jetstream"];
      return getRandomElementFromArray(names)
  }

  this.getRandomRageName = function(){
      var names = ["Rage", "Barbaric", "Impossible", "Juggalo","Horrorcore" ,"Madness", "Carnival", "Mirthful", "Screaming", "Berserk", "MoThErFuCkInG", "War", "Haze", "Murder", "Furioso", "Aggressive", "ATBasher", "Violent", "Unbound", "Purple", "Unholy", "Hateful", "Fearful", "Inconceivable", "Impossible"];
      return getRandomElementFromArray(names)
  }

  this.getRandomLifeName = function(){
      var names = ["Life" ,"Pastoral", "Green", "Relief", "Healing", "Plant", "Vitality", "Growing", "Garden", "Multiplying", "Rising", "Survival", "Phoenix", "Respawn", "Mangrit", "Animato", "Gaia", "Increasing", "Overgrowth", "Jungle", "Harvest", "Lazarus"];
      return getRandomElementFromArray(names)
  }

  this.getRandomHopeName = function(){
      var names = ["Hope","Fake", "Belief", "Bullshit", "Determination", "Burn", "Stubborn", "Religion", "Will", "Hero", "Undying", "Dream", "Sepulchritude", "Prophet", "Apocryphal"];
      return getRandomElementFromArray(names)
  }

  this.getRandomVoidName = function(){
      var randBonus = "" + getRandomElementFromArray(interests) +  ""
      var names = ["Void","Disappearification","Pumpkin", "Nothing", "Emptiness", "Invisible", "Dark", "Hole", "Solo", "Silent", "Alone", "Night", "Null", "[Censored]", "[???]", "Vacuous", "Abyss", "Noir", "Blank", "Tenebrous", "Antithesis", "404"];
      return getRandomElementFromArray(names)+ randBonus;
  }

  this.getRandomLightName = function(){
      var names = ["Lucky", "Light", "Knowledge", "Blinding", "Brilliant", "Break", "Blazing", "Glow", "Flare", "Gamble", "Omnifold", "Apollo", "Helios", "Scintillating", "Horseshoe", "Leggiero", "Star", "Kindle", "Gambit", "Blaze"];
      return getRandomElementFromArray(names)
  }

  this.getRandomMindName = function(){
      var names = ["Mind", "Modulation", "Shock", "Awe", "Coin", "Judgement", "Mind", "Decision", "Spark", "Path", "Trial", "Variations", "Thunder", "Logical", "Telekinetic", "Brainiac", "Hysteria", "Deciso", "Thesis", "Imagination",  "Psycho", "Control", "Execution", "Bolt"];
      return getRandomElementFromArray(names)
  }

  this.getRandomHeartName = function(){
      var names = ["Heart","Soul", "Jazz", "Blues", "Spirit", "Splintering", "Clone", "Self", "Havoc", "Noble", "Animus", "Astral", "Shatter", "Breakdown", "Ethereal", "Beat", "Pulchritude"];
      return getRandomElementFromArray(names)
  }

  this.getRandomBloodName = function(){
      var names = ["Blood", "Chain", "Flow", "Charge", "Awakening", "Ichorial", "Friendship", "Trusting", "Clotting", "Union", "Bleeding", "Team", "Transfusion", "Pulse", "Sanguine", "Positive", "Negative", "Cruor", "Vim", "Chorus", "Iron", "Ichorial", "Fever", "Heat"];
      return getRandomElementFromArray(names)
  }

  this.getRandomDoomName = function(){
      var names = ["Dark", "Broken", "Meteoric", "Diseased","Fate", "Doomed", "Inevitable", "Doom", "End", "Final", "Dead", "Ruin", "Rot", "Coffin", "Apocalypse", "Morendo", "Smorzando", "~Ath", "Armistyx", "Grave", "Corpse", "Ashen", "Reaper", "Diseased", "Armageddon", "Cursed"];
      return getRandomElementFromArray(names)
  }

  this.getRandomTimeName = function(){
      var names = ["Time","Paradox", "Moment", "Foregone", "Reset", "Endless", "Temporal", "Shenanigans", "Clock", "Tick-Tock", "Spinning", "Repeat", "Rhythm", "Redshift",  "Epoch", "Beatdown", "Slow", "Remix", "Clockwork", "Lock", "Eternal"];
      return getRandomElementFromArray(names)
  }

  this.getRandomSpaceName = function(){
      var names = ["Canon","Space","Frogs", "Location", "Spatial", "Universe", "Infinite", "Spiral", "Physics", "Star", "Galaxy", "Nuclear", "Atomic", "Nucleus", "Horizon", "Event", "CROAK", "Spatium", "Squiddle", "Engine", "Meteor", "Gravity", "Crush"];
      return getRandomElementFromArray(names)
  }

  this.getRandomNameForAspect = function(aspect){
    console.log(aspect);
    var ret = "";
    if(aspect == "Blood") ret = this.getRandomBloodName();
    if(aspect == "Mind") ret = this.getRandomMindName();
    if(aspect == "Rage") ret =  this.getRandomRageName();
    if(aspect == "Time") ret =  this.getRandomTimeName();
    if(aspect == "Void") ret =  this.getRandomVoidName();
    if(aspect == "Heart") ret =  this.getRandomHeartName();
    if(aspect == "Breath") ret =  this.getRandomBreathName();
    if(aspect == "Light") ret =  this.getRandomLightName();
    if(aspect == "Space") ret =  this.getRandomSpaceName();
    if(aspect == "Hope") ret =  this.getRandomHopeName();
    if(aspect == "Life") ret =  this.getRandomLifeName();
    if(aspect == "Doom") ret =  this.getRandomDoomName();
    return getFontColorFromAspect(aspect) + ret + ""
  }

  this.getRandomMusicWord = function(aspect){ //takes in an aspect for color
    var names = ["Fortississimo", "Leitmotif", "Liberetto", "Sarabande", "Serenade", "Anthem", "Crescendo", "Vivace", "Encore", "Vivante", "Allegretto", "Fugue", "Choir", "Nobilmente", "Hymn", "Eroico", "Chant", "Mysterioso", "Diminuendo", "Perdendo", "Staccato", "Allegro", "Caloroso", "Nocturne"];
    names = names.concat(["Cadenza", "Cadence", "Waltz", "Concerto", "Finale", "Requiem", "Coda", "Dirge", "Battaglia", "Leggiadro", "Capriccio", "Presto", "Largo", "Accelerando", "Polytempo", "Overture", "Reprise", "Orchestra"])

    var ret = getRandomElementFromArray(names);
    if(Math.seededRandom() > 0.5){
      return "" + ret.toLowerCase()+"";  //tacked onto existin word
    }else{
      return " " + ret; //extra word
    }
  }

  this.getFraymotifName = function(players, tier){
    var name = "";
    var indexOfMusic = players.length-1;  //used to be random now always at end.
    if(players.length == 1){
      indexOfMusic = getRandomInt(0,tier-1);
      for(var i = 0; i < tier; i++){
        var musicWord = "";
        if(i == indexOfMusic) musicWord = this.getRandomMusicWord(players[i].aspect);
        name += this.getRandomNameForAspect(players[0].aspect) + musicWord +" ";
      }
    }else{

      for(var i = 0; i<players.length; i++){
        var musicWord = "";
        if(i == indexOfMusic) musicWord = this.getRandomMusicWord(players[i].aspect);
        name += this.getRandomNameForAspect(players[i].aspect) + musicWord +  " ";
      }
    }
    return name;
  }

  //classes is between 0 and aspects.length. each aspect is paired with a class.
  //should there be no class to pair with, random effect based on aspect
  //otherwise, effect is based on both class and aspect
  this.makeFraymotif = function(players,tier){
    var name = this.getFraymotifName(players, tier);
	var aspects = [];
	for(var i = 0; i<players.length; i++){
		if(aspects.indexOf(players[i].aspect) == -1) aspects.push(players[i].aspect); //if multiple of the same aspect is included here, just ignore.
	}
    var f= new Fraymotif(aspects, name, tier);
	f.addEffectsForPlayers(players);
	return f;
  }
}

    //effects are frozen at creation, basically.  if this fraymotif is created by a Bard of Breath in a session with a Prince of Time,
    //who then dies, and then a combo session results in an Heir of Time being able to use it with the Bard of Breath, then it'll still have the prince effect.
function FraymotifEffect(){
	this.statName = "";  //RELATIONSHIP, hp AND LIFE are all treated differently.  (Life can only be cast on allies), hp is applied to currentHp rather than hp. otherwise same stuff as associatedStats.
	this.target = ""; //self, allies or enemy or enemies, 0, 1, 2, 3

	this.setEffectForPlayer = function(player){

	}
}<|endoftext|>


<commit_before>const transformOptionsFromPath = (options, relPathFromRoot) => {
  let transformedOptions = {};

  Object.keys(options).forEach(key => {
    const transformedOpt = transformOptionFromPath(options[key], relPathFromRoot);
    if (transformedOpt) {
      transformedOptions[key] = transformedOpt;
    }
  });

  return transformedOptions;
};

const transformOptionFromPath = (opt, relPathFromRoot) => {
  const lengthOfPath = relPathFromRoot.length;
  if (Array.isArray(opt)) {
    const updatedArray = opt
      .filter(
        optString => optString.startsWith(relPathFromRoot) && !(optString === relPathFromRoot)
      )
      .map(optString => optString.substring(lengthOfPath + 1));
    if (updatedArray.length === 0) {
      return false;
    }

    return updatedArray;
  }

  return opt;
};

module.exports = transformOptionsFromPath;
<commit_msg>782, updated transformOptionsFromPath method due PR comments<commit_after>function transformOptionFromPath(opt, relPathFromRoot) {
  const lengthOfPath = relPathFromRoot.length;
  if (Array.isArray(opt)) {
    const updatedArray = opt
      .filter(
        optString => optString.startsWith(relPathFromRoot) && !(optString === relPathFromRoot)
      )
      .map(optString => optString.substring(lengthOfPath + 1));
    if (updatedArray.length === 0) {
      return false;
    }

    return updatedArray;
  }

  return opt;
}

module.exports = (options, relPathFromRoot) => {
  return Object.keys(options).reduce((acc, key) => {
    const transformedOpt = transformOptionFromPath(options[key], relPathFromRoot);
    if (transformedOpt) {
      acc[key] = transformedOpt;
    }

    return acc;
  }, {});
};
<|endoftext|>