<commit_before>/*
 * Paper.js - The Swiss Army Knife of Vector Graphics Scripting.
 * http://paperjs.org/
 *
 * Copyright (c) 2011 - 2013, Juerg Lehni & Jonathan Puckey
 * http://lehni.org/ & http://jonathanpuckey.com/
 *
 * Distributed under the MIT license. See LICENSE file for details.
 *
 * All rights reserved.
 */

Path.inject({ statics: new function() {

	function createRectangle(/* rect */) {
		var rect = Rectangle.readNamed(arguments, 'rectangle'),
			left = rect.getLeft(),
			top = rect.getTop(),
			right = rect.getRight(),
			bottom = rect.getBottom(),
			path = new Path(Base.getNamed(arguments));
		path._add([
			new Segment(Point.create(left, bottom)),
			new Segment(Point.create(left, top)),
			new Segment(Point.create(right, top)),
			new Segment(Point.create(right, bottom))
		]);
		path._closed = true;
		return path;
	}

	// Kappa, see: http://www.whizkidtech.redprince.net/bezier/circle/kappa/
	var kappa = 2 * (Math.sqrt(2) - 1) / 3;

	var ellipseSegments = [
		new Segment([0, 0.5], [0, kappa ], [0, -kappa]),
		new Segment([0.5, 0], [-kappa, 0], [kappa, 0 ]),
		new Segment([1, 0.5], [0, -kappa], [0, kappa ]),
		new Segment([0.5, 1], [kappa, 0 ], [-kappa, 0])
	];

	function createEllipse(/* rect */) {
		var rect = Rectangle.readNamed(arguments, 'rectangle'),
			path = new Path(Base.getNamed(arguments)),
			point = rect.getPoint(true),
			size = rect.getSize(true),
			segments = new Array(4);
		for (var i = 0; i < 4; i++) {
			var segment = ellipseSegments[i];
			segments[i] = new Segment(
				segment._point.multiply(size).add(point),
				segment._handleIn.multiply(size),
				segment._handleOut.multiply(size)
			);
		}
		path._add(segments);
		path._closed = true;
		return path;
	}

	return /** @lends Path */{
		/**
		 * {@grouptitle Shaped Paths}
		 *
		 * Creates a Path Item with two anchor points forming a line.
		 *
		 * @param {Point} from the first anchor point of the path
		 * @param {Point} to the second anchor point of the path
		 * @return {Path} the newly created path
		 *
		 * @example {@paperscript}
		 * var from = new Point(20, 20);
		 * var to = new Point(80, 80);
		 * var path = new Path.Line(from, to);
		 * path.strokeColor = 'black';
		 *
		 * @example {@paperscript}
		 * var path = new Path.Line({
		 * 	from: new Point(20, 20),
		 * 	to: new Point(80, 80),
		 * 	strokeColor: 'black'
		 * });
		 */
		Line: function(/* from, to */) {
			return new Path(
				Point.readNamed(arguments, 'from'),
				Point.readNamed(arguments, 'to')
			).set(Base.getNamed(arguments));
		},

		/**
		 * Creates a rectangle shaped Path Item from the passed point and size.
		 *
		 * @name Path.Rectangle
		 * @param {Point} point
		 * @param {Size} size
		 * @return {Path} the newly created path
		 *
		 * @example {@paperscript}
		 * var point = new Point(20, 20);
		 * var size = new Size(60, 60);
		 * var rectangle = new Rectangle(point, size);
		 * var path = new Path.Rectangle(rectangle);
		 * path.strokeColor = 'black';
		 */
		/**
		 * Creates a rectangle shaped Path Item from the passed points. These do not
		 * necessarily need to be the top left and bottom right corners, the
		 * constructor figures out how to fit a rectangle between them.
		 *
		 * @name Path.Rectangle
		 * @param {Point} from The first point defining the rectangle
		 * @param {Point} to The second point defining the rectangle
		 * @return {Path} the newly created path
		 *
		 * @example {@paperscript}
		 * var from = new Point(20, 20);
		 * var to = new Point(80, 80);
		 * var path = new Path.Rectangle(from, to);
		 * path.strokeColor = 'black';
		 */
		/**
		 * Creates a rectangle shaped Path Item from the passed abstract
		 * {@link Rectangle}.
		 *
		 * @param {Rectangle} rectangle
		 * @return {Path} the newly created path
		 *
		 * @example {@paperscript}
		 * var rectangle = new Rectangle({
		 * 	point: new Point(20, 20),
		 * 	size: new Size(60, 60)
		 * });
		 * var path = new Path.Rectangle(rectangle);
		 * path.strokeColor = 'black';
		 */
		Rectangle: createRectangle,

		/**
		 * Creates a rectangular Path Item with rounded corners.
		 *
		 * @param {Rectangle} rectangle
		 * @param {Size} radius the size of the rounded corners
		 * @return {Path} the newly created path
		 *
		 * @example {@paperscript}
		 * var rectangle = new Rectangle({
		 * 	point: new Point(20, 20),
		 * 	size: new Size(60, 60)
		 * });
		 * var cornerSize = new Size(10, 10);
		 * var path = new Path.RoundRectangle(rectangle, cornerSize);
		 * path.strokeColor = 'black';
		 */
		RoundRectangle: function(/* rect, radius */) {
			var rect = Rectangle.readNamed(arguments, 'rectangle'),
				radius = Size.readNamed(arguments, 'radius');
			if (radius.isZero())
				return createRectangle(rect);
			radius = Size.min(radius, rect.getSize(true).divide(2));
			var bl = rect.getBottomLeft(true),
				tl = rect.getTopLeft(true),
				tr = rect.getTopRight(true),
				br = rect.getBottomRight(true),
				h = radius.multiply(kappa * 2), // handle vector
				path = new Path(Base.getNamed(arguments));
			path._add([
				new Segment(bl.add(radius.width, 0), null, [-h.width, 0]),
				new Segment(bl.subtract(0, radius.height), [0, h.height], null),

				new Segment(tl.add(0, radius.height), null, [0, -h.height]),
				new Segment(tl.add(radius.width, 0), [-h.width, 0], null),

				new Segment(tr.subtract(radius.width, 0), null, [h.width, 0]),
				new Segment(tr.add(0, radius.height), [0, -h.height], null),

				new Segment(br.subtract(0, radius.height), null, [0, h.height]),
				new Segment(br.subtract(radius.width, 0), [h.width, 0], null)
			]);
			path._closed = true;
			return path;
		},

		/**
		* Creates an ellipse shaped Path Item.
		 *
		 * @param {Rectangle} rectangle
		 * @param {Boolean} [circumscribed=false] when set to {@code true} the
		 *        ellipse shaped path will be created so the rectangle fits into
		 *        it. When set to {@code false} the ellipse path will fit within
		 *        the rectangle.
		 * @return {Path} the newly created path
		 *
		 * @example {@paperscript}
		 * var rectangle = new Rectangle({
		 * 	point: [20, 20],
		 * 	size: [180, 60]
		 * });
		 * var path = new Path.Ellipse(rectangle);
		 * path.fillColor = 'black';
		 */
		Ellipse: createEllipse,

		/**
		 * @deprecated use {@link #Path.Ellipse(rect)} instead.
		 */
		Oval: createEllipse,

		/**
		 * Creates a circle shaped Path Item.
		 *
		 * @param {Point} center the center point of the circle
		 * @param {Number} radius the radius of the circle
		 * @return {Path} the newly created path
		 *
		 * @example {@paperscript}
		 * var path = new Path.Circle(new Point(80, 50), 30);
		 * path.strokeColor = 'black';
		 */
		Circle: function(/* center, radius */) {
			var center = Point.readNamed(arguments, 'center'),
				radius = Base.readNamed(arguments, 'radius');
			return createEllipse(new Rectangle(center.subtract(radius),
					Size.create(radius * 2, radius * 2)))
					.set(Base.getNamed(arguments));
		},

		/**
		 * Creates a circular arc shaped Path Item.
		 *
		 * @param {Point} from the starting point of the circular arc
		 * @param {Point} through the point the arc passes through
		 * @param {Point} to the end point of the arc
		 * @return {Path} the newly created path
		 *
		 * @example {@paperscript}
		 * var from = new Point(20, 20);
		 * var through = new Point(60, 20);
		 * var to = new Point(80, 80);
		 * var path = new Path.Arc(from, through, to);
		 * path.strokeColor = 'black';
		 */
		Arc: function(/* from, through, to */) {
			var from = Point.readNamed(arguments, 'from'),
				through = Point.readNamed(arguments, 'through'),
				to = Point.readNamed(arguments, 'to'),
				path = new Path(Base.getNamed(arguments));
			path.moveTo(from);
			path.arcTo(through, to);
			return path;
		},

		/**
		 * Creates a regular polygon shaped Path Item.
		 *
		 * @param {Point} center the center point of the polygon
		 * @param {Number} numSides the number of sides of the polygon
		 * @param {Number} radius the radius of the polygon
		 * @return {Path} the newly created path
		 *
		 * @example {@paperscript}
		 * var center = new Point(50, 50);
		 * var sides = 3;
		 * var radius = 40;
		 * var triangle = new Path.RegularPolygon(center, sides, radius);
		 * triangle.fillColor = 'black';
		 *
		 * @example {@paperscript}
		 * var center = new Point(50, 50);
		 * var sides = 10;
		 * var radius = 40;
		 * var triangle = new Path.RegularPolygon(center, sides, radius);
		 * triangle.fillColor = 'black';
		 */
		RegularPolygon: function(/* center, numSides, radius */) {
			var center = Point.readNamed(arguments, 'center'),
				numSides = Base.readNamed(arguments, 'numSides'),
				radius = Base.readNamed(arguments, 'radius'),
				path = new Path(Base.getNamed(arguments)),
				step = 360 / numSides,
				three = !(numSides % 3),
				vector = new Point(0, three ? -radius : radius),
				offset = three ? -1 : 0.5,
				segments = new Array(numSides);
			for (var i = 0; i < numSides; i++) {
				segments[i] = new Segment(center.add(
					vector.rotate((i + offset) * step)));
			}
			path._add(segments);
			path._closed = true;
			return path;
		},

		/**
		 * Creates a star shaped Path Item.
		 *
		 * The largest of {@code radius1} and {@code radius2} will be the outer
		 * radius of the star. The smallest of radius1 and radius2 will be the
		 * inner radius.
		 *
		 * @param {Point} center the center point of the star
		 * @param {Number} numPoints the number of points of the star
		 * @param {Number} radius1
		 * @param {Number} radius2
		 * @return {Path} the newly created path
		 *
		 * @example {@paperscript}
		 * var center = new Point(50, 50);
		 * var points = 12;
		 * var radius1 = 25;
		 * var radius2 = 40;
		 * var path = new Path.Star(center, points, radius1, radius2);
		 * path.fillColor = 'black';
		 */
		Star: function(/* center, numPoints, radius1, radius2 */) {
			var center = Point.readNamed(arguments, 'center'),
				numPoints = Base.readNamed(arguments, 'numPoints') * 2,
				radius1 = Base.readNamed(arguments, 'radius1'),
				radius2 = Base.readNamed(arguments, 'radius2'),
				path = new Path(Base.getNamed(arguments)),
				step = 360 / numPoints,
				vector = new Point(0, -1),
				segments = new Array(numPoints);
			for (var i = 0; i < numPoints; i++) {
				segments[i] = new Segment(center.add(
					vector.rotate(step * i).multiply(i % 2 ? radius2 : radius1)));
			}
			path._add(segments);
			path._closed = true;
			return path;
		}
	};
}});
<commit_msg>Fix issue with object literal properties being accidentally misread as path segments.<commit_after>/*
 * Paper.js - The Swiss Army Knife of Vector Graphics Scripting.
 * http://paperjs.org/
 *
 * Copyright (c) 2011 - 2013, Juerg Lehni & Jonathan Puckey
 * http://lehni.org/ & http://jonathanpuckey.com/
 *
 * Distributed under the MIT license. See LICENSE file for details.
 *
 * All rights reserved.
 */

Path.inject({ statics: new function() {

	function createPath(args) {
		return new Path().set(Base.getNamed(args));
	}

	function createRectangle(/* rect */) {
		var rect = Rectangle.readNamed(arguments, 'rectangle'),
			left = rect.getLeft(),
			top = rect.getTop(),
			right = rect.getRight(),
			bottom = rect.getBottom(),
			path = createPath(arguments);
		path._add([
			new Segment(Point.create(left, bottom)),
			new Segment(Point.create(left, top)),
			new Segment(Point.create(right, top)),
			new Segment(Point.create(right, bottom))
		]);
		path._closed = true;
		return path;
	}

	// Kappa, see: http://www.whizkidtech.redprince.net/bezier/circle/kappa/
	var kappa = 2 * (Math.sqrt(2) - 1) / 3;

	var ellipseSegments = [
		new Segment([0, 0.5], [0, kappa ], [0, -kappa]),
		new Segment([0.5, 0], [-kappa, 0], [kappa, 0 ]),
		new Segment([1, 0.5], [0, -kappa], [0, kappa ]),
		new Segment([0.5, 1], [kappa, 0 ], [-kappa, 0])
	];

	function createEllipse(/* rect */) {
		var rect = Rectangle.readNamed(arguments, 'rectangle'),
			path = createPath(arguments),
			point = rect.getPoint(true),
			size = rect.getSize(true),
			segments = new Array(4);
		for (var i = 0; i < 4; i++) {
			var segment = ellipseSegments[i];
			segments[i] = new Segment(
				segment._point.multiply(size).add(point),
				segment._handleIn.multiply(size),
				segment._handleOut.multiply(size)
			);
		}
		path._add(segments);
		path._closed = true;
		return path;
	}

	return /** @lends Path */{
		/**
		 * {@grouptitle Shaped Paths}
		 *
		 * Creates a Path Item with two anchor points forming a line.
		 *
		 * @param {Point} from the first anchor point of the path
		 * @param {Point} to the second anchor point of the path
		 * @return {Path} the newly created path
		 *
		 * @example {@paperscript}
		 * var from = new Point(20, 20);
		 * var to = new Point(80, 80);
		 * var path = new Path.Line(from, to);
		 * path.strokeColor = 'black';
		 *
		 * @example {@paperscript}
		 * var path = new Path.Line({
		 * 	from: new Point(20, 20),
		 * 	to: new Point(80, 80),
		 * 	strokeColor: 'black'
		 * });
		 */
		Line: function(/* from, to */) {
			return new Path(
				Point.readNamed(arguments, 'from'),
				Point.readNamed(arguments, 'to')
			).set(Base.getNamed(arguments));
		},

		/**
		 * Creates a rectangle shaped Path Item from the passed point and size.
		 *
		 * @name Path.Rectangle
		 * @param {Point} point
		 * @param {Size} size
		 * @return {Path} the newly created path
		 *
		 * @example {@paperscript}
		 * var point = new Point(20, 20);
		 * var size = new Size(60, 60);
		 * var rectangle = new Rectangle(point, size);
		 * var path = new Path.Rectangle(rectangle);
		 * path.strokeColor = 'black';
		 */
		/**
		 * Creates a rectangle shaped Path Item from the passed points. These do not
		 * necessarily need to be the top left and bottom right corners, the
		 * constructor figures out how to fit a rectangle between them.
		 *
		 * @name Path.Rectangle
		 * @param {Point} from The first point defining the rectangle
		 * @param {Point} to The second point defining the rectangle
		 * @return {Path} the newly created path
		 *
		 * @example {@paperscript}
		 * var from = new Point(20, 20);
		 * var to = new Point(80, 80);
		 * var path = new Path.Rectangle(from, to);
		 * path.strokeColor = 'black';
		 */
		/**
		 * Creates a rectangle shaped Path Item from the passed abstract
		 * {@link Rectangle}.
		 *
		 * @param {Rectangle} rectangle
		 * @return {Path} the newly created path
		 *
		 * @example {@paperscript}
		 * var rectangle = new Rectangle({
		 * 	point: new Point(20, 20),
		 * 	size: new Size(60, 60)
		 * });
		 * var path = new Path.Rectangle(rectangle);
		 * path.strokeColor = 'black';
		 */
		Rectangle: createRectangle,

		/**
		 * Creates a rectangular Path Item with rounded corners.
		 *
		 * @param {Rectangle} rectangle
		 * @param {Size} radius the size of the rounded corners
		 * @return {Path} the newly created path
		 *
		 * @example {@paperscript}
		 * var rectangle = new Rectangle({
		 * 	point: new Point(20, 20),
		 * 	size: new Size(60, 60)
		 * });
		 * var cornerSize = new Size(10, 10);
		 * var path = new Path.RoundRectangle(rectangle, cornerSize);
		 * path.strokeColor = 'black';
		 */
		RoundRectangle: function(/* rect, radius */) {
			var rect = Rectangle.readNamed(arguments, 'rectangle'),
				radius = Size.readNamed(arguments, 'radius');
			if (radius.isZero())
				return createRectangle(rect);
			radius = Size.min(radius, rect.getSize(true).divide(2));
			var bl = rect.getBottomLeft(true),
				tl = rect.getTopLeft(true),
				tr = rect.getTopRight(true),
				br = rect.getBottomRight(true),
				h = radius.multiply(kappa * 2), // handle vector
				path = createPath(arguments);
			path._add([
				new Segment(bl.add(radius.width, 0), null, [-h.width, 0]),
				new Segment(bl.subtract(0, radius.height), [0, h.height], null),

				new Segment(tl.add(0, radius.height), null, [0, -h.height]),
				new Segment(tl.add(radius.width, 0), [-h.width, 0], null),

				new Segment(tr.subtract(radius.width, 0), null, [h.width, 0]),
				new Segment(tr.add(0, radius.height), [0, -h.height], null),

				new Segment(br.subtract(0, radius.height), null, [0, h.height]),
				new Segment(br.subtract(radius.width, 0), [h.width, 0], null)
			]);
			path._closed = true;
			return path;
		},

		/**
		* Creates an ellipse shaped Path Item.
		 *
		 * @param {Rectangle} rectangle
		 * @param {Boolean} [circumscribed=false] when set to {@code true} the
		 *        ellipse shaped path will be created so the rectangle fits into
		 *        it. When set to {@code false} the ellipse path will fit within
		 *        the rectangle.
		 * @return {Path} the newly created path
		 *
		 * @example {@paperscript}
		 * var rectangle = new Rectangle({
		 * 	point: [20, 20],
		 * 	size: [180, 60]
		 * });
		 * var path = new Path.Ellipse(rectangle);
		 * path.fillColor = 'black';
		 */
		Ellipse: createEllipse,

		/**
		 * @deprecated use {@link #Path.Ellipse(rect)} instead.
		 */
		Oval: createEllipse,

		/**
		 * Creates a circle shaped Path Item.
		 *
		 * @param {Point} center the center point of the circle
		 * @param {Number} radius the radius of the circle
		 * @return {Path} the newly created path
		 *
		 * @example {@paperscript}
		 * var path = new Path.Circle(new Point(80, 50), 30);
		 * path.strokeColor = 'black';
		 */
		Circle: function(/* center, radius */) {
			var center = Point.readNamed(arguments, 'center'),
				radius = Base.readNamed(arguments, 'radius');
			return createEllipse(new Rectangle(center.subtract(radius),
					Size.create(radius * 2, radius * 2)))
					.set(Base.getNamed(arguments));
		},

		/**
		 * Creates a circular arc shaped Path Item.
		 *
		 * @param {Point} from the starting point of the circular arc
		 * @param {Point} through the point the arc passes through
		 * @param {Point} to the end point of the arc
		 * @return {Path} the newly created path
		 *
		 * @example {@paperscript}
		 * var from = new Point(20, 20);
		 * var through = new Point(60, 20);
		 * var to = new Point(80, 80);
		 * var path = new Path.Arc(from, through, to);
		 * path.strokeColor = 'black';
		 */
		Arc: function(/* from, through, to */) {
			var from = Point.readNamed(arguments, 'from'),
				through = Point.readNamed(arguments, 'through'),
				to = Point.readNamed(arguments, 'to'),
				path = createPath(arguments);
			path.moveTo(from);
			path.arcTo(through, to);
			return path;
		},

		/**
		 * Creates a regular polygon shaped Path Item.
		 *
		 * @param {Point} center the center point of the polygon
		 * @param {Number} numSides the number of sides of the polygon
		 * @param {Number} radius the radius of the polygon
		 * @return {Path} the newly created path
		 *
		 * @example {@paperscript}
		 * var center = new Point(50, 50);
		 * var sides = 3;
		 * var radius = 40;
		 * var triangle = new Path.RegularPolygon(center, sides, radius);
		 * triangle.fillColor = 'black';
		 *
		 * @example {@paperscript}
		 * var center = new Point(50, 50);
		 * var sides = 10;
		 * var radius = 40;
		 * var triangle = new Path.RegularPolygon(center, sides, radius);
		 * triangle.fillColor = 'black';
		 */
		RegularPolygon: function(/* center, numSides, radius */) {
			var center = Point.readNamed(arguments, 'center'),
				numSides = Base.readNamed(arguments, 'numSides'),
				radius = Base.readNamed(arguments, 'radius'),
				path = createPath(arguments),
				step = 360 / numSides,
				three = !(numSides % 3),
				vector = new Point(0, three ? -radius : radius),
				offset = three ? -1 : 0.5,
				segments = new Array(numSides);
			for (var i = 0; i < numSides; i++) {
				segments[i] = new Segment(center.add(
					vector.rotate((i + offset) * step)));
			}
			path._add(segments);
			path._closed = true;
			return path;
		},

		/**
		 * Creates a star shaped Path Item.
		 *
		 * The largest of {@code radius1} and {@code radius2} will be the outer
		 * radius of the star. The smallest of radius1 and radius2 will be the
		 * inner radius.
		 *
		 * @param {Point} center the center point of the star
		 * @param {Number} numPoints the number of points of the star
		 * @param {Number} radius1
		 * @param {Number} radius2
		 * @return {Path} the newly created path
		 *
		 * @example {@paperscript}
		 * var center = new Point(50, 50);
		 * var points = 12;
		 * var radius1 = 25;
		 * var radius2 = 40;
		 * var path = new Path.Star(center, points, radius1, radius2);
		 * path.fillColor = 'black';
		 */
		Star: function(/* center, numPoints, radius1, radius2 */) {
			var center = Point.readNamed(arguments, 'center'),
				numPoints = Base.readNamed(arguments, 'numPoints') * 2,
				radius1 = Base.readNamed(arguments, 'radius1'),
				radius2 = Base.readNamed(arguments, 'radius2'),
				path = createPath(arguments),
				step = 360 / numPoints,
				vector = new Point(0, -1),
				segments = new Array(numPoints);
			for (var i = 0; i < numPoints; i++) {
				segments[i] = new Segment(center.add(
					vector.rotate(step * i).multiply(i % 2 ? radius2 : radius1)));
			}
			path._add(segments);
			path._closed = true;
			return path;
		}
	};
}});
<|endoftext|>


<commit_before>import test from 'tape'
import {createAction} from 'redux-actions'
import boot, {BOOT} from '../src/bootstrap'

test('Use redux-actions with redux-promise to fire async side-effect actions in middlewares', assert => {

  const AFTER_BOOT = 'redux-boot/test/AFTER_BOOT'
  const AFTER_AFTER_BOOT = 'redux-boot/test/AFTER_AFTER_BOOT'

  // Mocking an API.
  let data = {
    1: 'bar',
    2: 'baz',
    3: 'rock'
  }

  const someApi = {
    get(id) {
      return Promise.resolve({
        name: data[id]
      })
    }
  }

  // Asynchronous side-effect action.
  const afterBootAction = createAction(AFTER_BOOT, async id => {

    // Geting something from an API.
    const result = await someApi.get(id)
    return result
  })

  // Synchronous side-effect action.
  const afterAfterBootAction = createAction(AFTER_AFTER_BOOT)

  const initialState = {
    foo: 'bar'
  }

  // Module with async side-effect.
  const testAsyncModule = {

    reducer: {
      // Reaction to after boot side-effect.
      [AFTER_BOOT]: (state, action) => {
        assert.pass('Reducer called by an "after boot" side-effect')

        return {
          ...state,
          foo: action.payload
        }
      }
    },

    middleware({getState, dispatch}) {
      // Note the async keyword.
      return next => async action => {
        let nextResult = next(action)

        // Reaction to the boostrap action.
        if (action.type === BOOT) {
          assert.pass('Middleware called at bootstrap action')

          // Dispatch after boot side-effects.
          const sideEffect = await dispatch(afterBootAction(3))

          assert.equal(
            getState().foo.name,
            'rock',
            'Async "after boot" side-effect was handled'
          )
        }

        return nextResult
      }
    }
  }

  // Module with sync side-effect.
  const testSyncModule = {
    reducer: {
      [AFTER_AFTER_BOOT]: (state, action) => {
        assert.pass('Reducer called by an "after, after boot" side-effect')

        return {
          ...state,
          afterAfterBoot: true
        }
      }
    },
    middleware({getState, dispatch}) {
      return next => action => {

        let nextResult = next(action)

        if (action.type === AFTER_BOOT) {
          assert.pass('Middleware called by side-effect')

          // Sync side-effect action is fully dispatched.
          const sideEffect = dispatch(afterAfterBootAction())
        }

        if (action.type == AFTER_AFTER_BOOT) {
          assert.pass('Middleware of sync module reacted to an "after, after boot" side-effect')
        }

        return nextResult
      }
    }
  }

  const modules = [
    testAsyncModule,
    testSyncModule
  ]

  const app = boot(initialState, modules)

  app.then(({action, store}) => {

    assert.ok(
      store.getState().afterAfterBoot,
      'After after side-effect was handled'
    )

    assert.equal(
      store.getState().foo.name,
      'rock',
      'Async bootstrap and all theirs side-effects were handled'
    )

    assert.end()
  })
})
<commit_msg>Added setTimeout on async side-effects test.<commit_after>import test from 'tape'
import {createAction} from 'redux-actions'
import boot, {BOOT} from '../src/bootstrap'

test('Use redux-actions with redux-promise to fire async side-effect actions in middlewares', assert => {

  const AFTER_BOOT = 'redux-boot/test/AFTER_BOOT'
  const AFTER_AFTER_BOOT = 'redux-boot/test/AFTER_AFTER_BOOT'

  // Mocking an API.
  let data = {
    1: 'bar',
    2: 'baz',
    3: 'rock'
  }

  const someApi = {
    get(id) {
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          resolve({name: data[id]})
        }, 1000)
      })
    }
  }

  // Asynchronous side-effect action.
  const afterBootAction = createAction(AFTER_BOOT, async id => {

    // Geting something from an API.
    const result = await someApi.get(id)
    return result
  })

  // Synchronous side-effect action.
  const afterAfterBootAction = createAction(AFTER_AFTER_BOOT)

  const initialState = {
    foo: 'bar'
  }

  // Module with async side-effect.
  const testAsyncModule = {

    reducer: {
      // Reaction to after boot side-effect.
      [AFTER_BOOT]: (state, action) => {
        assert.pass('Reducer called by an "after boot" side-effect')

        return {
          ...state,
          foo: action.payload
        }
      }
    },

    middleware({getState, dispatch}) {
      // Note the async keyword.
      return next => async action => {
        let nextResult = next(action)

        // Reaction to the boostrap action.
        if (action.type === BOOT) {
          assert.pass('Middleware called at bootstrap action')

          // Dispatch after boot side-effects.
          const sideEffect = await dispatch(afterBootAction(3))

          assert.equal(
            getState().foo.name,
            'rock',
            'Async "after boot" side-effect was handled'
          )
        }

        return nextResult
      }
    }
  }

  // Module with sync side-effect.
  const testSyncModule = {
    reducer: {
      [AFTER_AFTER_BOOT]: (state, action) => {
        assert.pass('Reducer called by an "after, after boot" side-effect')

        return {
          ...state,
          afterAfterBoot: true
        }
      }
    },
    middleware({getState, dispatch}) {
      return next => action => {

        let nextResult = next(action)

        if (action.type === AFTER_BOOT) {
          assert.pass('Middleware called by side-effect')

          // Sync side-effect action is fully dispatched.
          const sideEffect = dispatch(afterAfterBootAction())
        }

        if (action.type == AFTER_AFTER_BOOT) {
          assert.pass('Middleware of sync module reacted to an "after, after boot" side-effect')
        }

        return nextResult
      }
    }
  }

  const modules = [
    testAsyncModule,
    testSyncModule
  ]

  const app = boot(initialState, modules)

  app.then(({action, store}) => {

    assert.ok(
      store.getState().afterAfterBoot,
      'After after side-effect was handled'
    )

    assert.equal(
      store.getState().foo.name,
      'rock',
      'Async bootstrap and all theirs side-effects were handled'
    )

    assert.end()
  })
})
<|endoftext|>


<commit_before>var MongoClient = require('mongodb').MongoClient;

module.exports = {
	getConnection: function(address, callback){
		return getConnection(address, callback);
	}
};

function getConnection(address, callback){
	var connection = MongoClient.connect(
		address,
		function(err){
			if(err){
				callback(err);
			}else{
				callback(null);
			}
		}
	);
}<commit_msg>fix bug of not being able to find the collection in database<commit_after>var MongoClient = require('mongodb').MongoClient;

module.exports = {
	getConnection: function(address, callback){
		return getConnection(address, callback);
	}
};

function getConnection(address, callback){
	var connection = MongoClient.connect(
		address,
		function(err, db){
			if(err){
				callback(err);
			}else{
				callback(null, db);
			}
		}
	);
}<|endoftext|>


<commit_before>/*
This file is part of the Juju GUI, which lets users view and manage Juju
environments within a graphical interface (https://launchpad.net/juju-gui).
Copyright (C) 2012-2013 Canonical Ltd.

This program is free software: you can redistribute it and/or modify it under
the terms of the GNU Affero General Public License version 3, as published by
the Free Software Foundation.

This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero
General Public License for more details.

You should have received a copy of the GNU Affero General Public License along
with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

'use strict';

describe('ChangesUtils', function() {
  var db, ECS, ecs, models, changesUtils, Y;

  before(function(done) {
    var requirements = [
      'changes-utils',
      'environment-change-set',
      'juju-models',
      'node'
    ];
    Y = YUI(GlobalConfig).use(requirements, function(Y) {
      models = Y.namespace('juju.models');
      ECS = Y.namespace('juju').EnvironmentChangeSet;
      changesUtils = window.juju.utils.ChangesUtils;
      done();
    });
  });

  beforeEach(function() {
    db = new models.Database();
    ecs = new ECS({db: db});
  });

  afterEach(function() {
    window.clearTimeout(ecs.descriptionTimer);
    ecs.destroy();
  });

  // Add a service and a unit to the given database.
  var addEntities = function(db) {
    db.services.add({
      id: 'django', charm: 'cs:trusty/django-1', icon: 'django.svg'});
    db.addUnits({id: 'django/0'});
  };

  describe('getServiceByUnitId', function() {
    it('returns the service', function() {
      addEntities(db);
      var service = changesUtils.getServiceByUnitId('django/0',
        db.services, db.units);
      assert.strictEqual(service.get('id'), 'django');
    });

    it('raises an error if the service is not found', function() {
      assert.throw(function() {
        changesUtils.getServiceByUnitId('no-such/42', db.services, db.units);
      }, 'unit no-such/42 not found');
    });
  });

  it('can convert relation endpoints to their real names', function() {
    var services = new Y.ModelList();
    var args = [
      ['wordpress', {
        name: 'db',
        role: 'server'
      }],
      ['84882221$', {
        name: 'db',
        role: 'client'
      }],
      function() {}
    ];
    services.add([
      { id: 'foobar' },
      { id: '84882221$', displayName: '(mysql)' },
      { id: 'wordpress', displayName: 'wordpress' }
    ]);
    var services = changesUtils.getRealRelationEndpointNames(args, services);
    assert.deepEqual(services, ['mysql', 'wordpress']);
  });

  it('can generate descriptions for any change type', function() {
    addEntities(db);
    var tests = [{
      icon: 'django.svg',
      msg: ' cs:trusty/django-1 will be added to the controller.',
      change: {
        command: {
          method: '_addCharm',
          args: ['cs:trusty/django-1', 'cookies are better'],
          options: {applicationId: 'django'}
        }
      }
    }, {
      icon: 'django.svg',
      msg: ' django resources will be added.',
      change: {
        command: {
          method: '_addPendingResources',
          args: [{applicationName: 'django', charmURL: 'cs:trusty/django-1'}]
        }
      }
    }, {
      icon: 'django.svg',
      msg: ' django will be added to the model.',
      change: {
        command: {
          method: '_deploy',
          args: ['cs:trusty/django-1', 'django'],
          options: {modelId: 'django'}
        }
      },
      time: '12:34 PM'
    }, {
      icon: 'changes-units-added',
      msg: ' 1 django unit will be added.',
      change: {
        command: {
          method: '_add_unit',
          args: ['django', 1],
          options: {modelId: 'django/0'}
        }
      }
    }, {
      icon: 'changes-units-removed',
      msg: '1 unit will be removed from foo',
      change: {
        command: {
          method: '_remove_units',
          args: [['foo/0']]
        }
      }
    }, {
      // Note that this case is never used in production code.
      // We always add a single unit to a service.
      icon: 'changes-units-added',
      msg: ' 2 django units will be added.',
      change: {
        command: {
          method: '_add_unit',
          args: ['django', 2],
          options: {modelId: 'django/0'}
        }
      }
    }, {
      icon: 'changes-relation-added',
      msg: 'bar relation will be added between foo and baz.',
      change: {
        command: {
          method: '_add_relation',
          args: [
            ['foo', { name: 'bar' }],
            ['baz']
          ]
        }
      }
    }, {
      icon: 'changes-container-created',
      msg: '1 container will be added.',
      change: {
        command: {
          method: '_addMachines',
          args: [[{ parentId: 1 }]]
        }
      }
    }, {
      icon: 'changes-container-created',
      msg: '2 containers will be added.',
      change: {
        command: {
          method: '_addMachines',
          args: [[{ parentId: 1 }, { parentId: 1 }]]
        }
      }
    }, {
      icon: 'changes-machine-created',
      msg: '1 machine will be added.',
      change: {
        command: {
          method: '_addMachines',
          args: [[{}]]
        }
      }
    }, {
      icon: 'changes-machine-created',
      msg: '2 machines will be added.',
      change: {
        command: {
          method: '_addMachines',
          args: [[{}, {}]]
        }
      }
    }, {
      icon: 'changes-config-changed',
      msg: 'Configuration values will be changed for django.',
      change: {
        command: {
          method: '_set_config',
          args: ['django']
        }
      }
    }, {
      icon: 'changes-unknown',
      msg: 'An unknown change has been made to this model via the CLI.',
      change: {
        command: {
          method: '_anUnknownMethod'
        }
      }
    }];
    // This method needs to be stubbed out for the add relation path.
    var endpointNames = sinon.stub(
      changesUtils, 'getRealRelationEndpointNames').returns(['foo', 'baz']);
    this._cleanups.push(endpointNames.restore);
    tests.forEach(function(test) {
      var change = changesUtils.generateChangeDescription(
        db.services, db.units, test.change, true);
      assert.equal(change.icon, test.icon);
      assert.equal(change.description, test.msg);
      if (test.timestamp) {
        assert.equal(change.time, test.time);
      } else {
        assert.equal(change.time, '-');
      }
    });
  });

  it('can generate descriptions for all the changes in the ecs', function() {
    var stubDescription = sinon.stub(
      changesUtils,
      'generateChangeDescription');
    this._cleanups.push(stubDescription.restore);
    ecs.changeSet = { foo: { index: 0 }, bar: { index: 0 } };
    changesUtils.generateAllChangeDescriptions(
      db.services, db.units, ecs.changeSet);
    assert.equal(stubDescription.callCount, 2);
  });

  it('can get the counts of each type of ecs change', function() {
    var changeSet = {
      one: {command: {method: 'addMachine'}},
      two: {command: {method: 'addUnit'}},
      three: {command: {method: 'addMachine'}}
    };
    assert.deepEqual(changesUtils.getChangeCounts(changeSet), {
      addMachine: 2,
      addUnit: 1,
      total: 3
    });
  });

  it('can group the ecs change by type', function() {
    var changeSet = {
      one: {command: {method: 'addMachine'}},
      two: {command: {method: 'addUnit'}},
      three: {command: {method: 'addMachine'}}
    };
    assert.deepEqual(changesUtils.getGroupedChanges(changeSet), {
      addUnit: {
        two: {command: {method: 'addUnit'}}
      },
      addMachine: {
        one: {command: {method: 'addMachine'}},
        three: {command: {method: 'addMachine'}}
      }
    });
  });

  it('can filter the changeset by parent', function() {
    var changeSet = {
      one: {parents: ['parent1']},
      two: {parents: ['parent2']},
      three: {parents: ['parent1', 'parent2']}
    };
    assert.deepEqual(changesUtils.filterByParent(changeSet, 'parent1'), {
      one: {parents: ['parent1']},
      three: {parents: ['parent1', 'parent2']}
    });
  });

  describe('sortDescriptionsByApplication', function() {
    const getServiceById = sinon.stub();

    const descriptionsJSON = '[{"id":"addPendingResources-458","icon":"https://api.jujucharms.com/charmstore/v5/~containers/easyrsa-12/icon.svg","description":" easyrsa resources will be added.","time":"8:49 am"},{"id":"service-220","icon":"https://api.jujucharms.com/charmstore/v5/~containers/kubernetes-master-35/icon.svg","description":" kubernetes-master will be added to the model.","time":"8:49 am"},{"id":"expose-102","icon":"exposed_16","description":"kubernetes-master will be exposed","time":"8:49 am"},{"id":"addRelation-83","icon":"changes-relation-added","description":"kube-api-endpoint relation will be added between kubernetes-master and kubernetes-worker.","time":"8:49 am"},{"id":"addUnits-256","icon":"changes-units-added","description":" 1 easyrsa unit will be added.","time":"8:49 am"}]'; // eslint-disable-line max-len

    it('sorts additive descriptions by application', function() {
      const descriptions = JSON.parse(descriptionsJSON);
      const changeSet = {
        'addPendingResources-458': {
          command: {
            method: '_addPendingResources',
            args: [{applicationName: 'easyrsa'}]}},
        'service-220': {
          command: {
            method: '_deploy',
            args: [{applicationName: 'kubernetes-master'}]}},
        'expose-102': {
          command: {
            method: '_expose',
            args: ['kubernetes-master']}},
        'addRelation-83': {
          command: {
            method: '_add_relation',
            args: [['kubernetes-worker'], ['kubernetes-master']]}},
        'addUnits-256': {
          command: {
            method: '_add_unit',
            args: ['easyrsa']}}
      };
      const sorted = changesUtils.sortDescriptionsByApplication(
        getServiceById, changeSet, descriptions);
      const expected = {
        easyrsa: [
          {
            id: 'addPendingResources-458',
            icon: 'https://api.jujucharms.com/charmstore/v5/~containers/easyrsa-12/icon.svg', // eslint-disable-line max-len
            description: ' easyrsa resources will be added.',
            time: '8:49 am'
          },
          {
            id: 'addUnits-256',
            icon: 'changes-units-added',
            description: ' 1 easyrsa unit will be added.',
            time: '8:49 am'
          }
        ],
        'kubernetes-master': [{
          id: 'service-220',
          icon: 'https://api.jujucharms.com/charmstore/v5/~containers/kubernetes-master-35/icon.svg', // eslint-disable-line max-len
          description: ' kubernetes-master will be added to the model.',
          time: '8:49 am'
        }, {
          id: 'expose-102',
          icon: 'exposed_16',
          description: 'kubernetes-master will be exposed',
          time: '8:49 am'
        }, {
          id: 'addRelation-83',
          icon: 'changes-relation-added',
          description: 'kube-api-endpoint relation will be added between kubernetes-master and kubernetes-worker.', // eslint-disable-line max-len
          time: '8:49 am'
        }],
        'kubernetes-worker': [{
          id: 'addRelation-83',
          icon: 'changes-relation-added',
          description: 'kube-api-endpoint relation will be added between kubernetes-master and kubernetes-worker.', //eslint-disable-line max-len
          time: '8:49 am'
        }]
      };
      assert.deepEqual(sorted, expected);
    });

    it('skips methods in the blacklist', function() {
      const descriptions = [
        {id: 'addCharm-123'},
        {id: 'addMachines-123'},
        {id: 'addSSHKeys-123'},
        {id: 'importSSHKeys-123'},
        {id: 'destroyMachines-123'}
      ];
      const changeSet = {};
      const sorted = changesUtils.sortDescriptionsByApplication(
        getServiceById, changeSet, descriptions);
      assert.deepEqual(sorted, {});
    });

    it('does not error for removal commands', function() {
      const descriptions = [
        JSON.parse('{"id":"removeUnit-376","icon":"changes-units-removed","description":"1 unit will be removed from elasticsearch","time":"3:18 pm"}'), // eslint-disable-line max-len
        JSON.parse('{"id":"removeRelation-281","icon":"changes-relation-removed","description":"rest relation will be removed between kibana and elasticsearch.","time":"3:21 pm"}') // eslint-disable-line max-len
      ];

      const changeSet = {
        'removeUnit-376': {
          command: {
            method: '_remove_units',
            args: [['elasticsearch/0']]}},
        'removeRelation-281': {
          command: {
            method: '_remove_relation',
            args: [['kibana'],['elasticsearch']]
          }
        }
      };
      const sorted = changesUtils.sortDescriptionsByApplication(
        getServiceById, changeSet, descriptions);
      const expected = {
        elasticsearch: [
          {
            id: 'removeUnit-376',
            icon: 'changes-units-removed',
            description: '1 unit will be removed from elasticsearch',
            time: '3:18 pm'
          },
          {
            id: 'removeRelation-281',
            icon: 'changes-relation-removed',
            description: 'rest relation will be removed between kibana and elasticsearch.', // eslint-disable-line max-len
            time: '3:21 pm'
          }
        ],
        kibana: [
          {
            id: 'removeRelation-281',
            icon: 'changes-relation-removed',
            description: 'rest relation will be removed between kibana and elasticsearch.', // eslint-disable-line max-len
            time: '3:21 pm'
          }
        ]
      };
      assert.deepEqual(sorted, expected);
    });
  });
});
<commit_msg>formatting<commit_after>/*
This file is part of the Juju GUI, which lets users view and manage Juju
environments within a graphical interface (https://launchpad.net/juju-gui).
Copyright (C) 2012-2013 Canonical Ltd.

This program is free software: you can redistribute it and/or modify it under
the terms of the GNU Affero General Public License version 3, as published by
the Free Software Foundation.

This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero
General Public License for more details.

You should have received a copy of the GNU Affero General Public License along
with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

'use strict';

describe('ChangesUtils', function() {
  var db, ECS, ecs, models, changesUtils, Y;

  before(function(done) {
    var requirements = [
      'changes-utils',
      'environment-change-set',
      'juju-models',
      'node'
    ];
    Y = YUI(GlobalConfig).use(requirements, function(Y) {
      models = Y.namespace('juju.models');
      ECS = Y.namespace('juju').EnvironmentChangeSet;
      changesUtils = window.juju.utils.ChangesUtils;
      done();
    });
  });

  beforeEach(function() {
    db = new models.Database();
    ecs = new ECS({db: db});
  });

  afterEach(function() {
    window.clearTimeout(ecs.descriptionTimer);
    ecs.destroy();
  });

  // Add a service and a unit to the given database.
  var addEntities = function(db) {
    db.services.add({
      id: 'django', charm: 'cs:trusty/django-1', icon: 'django.svg'});
    db.addUnits({id: 'django/0'});
  };

  describe('getServiceByUnitId', function() {
    it('returns the service', function() {
      addEntities(db);
      var service = changesUtils.getServiceByUnitId('django/0',
        db.services, db.units);
      assert.strictEqual(service.get('id'), 'django');
    });

    it('raises an error if the service is not found', function() {
      assert.throw(function() {
        changesUtils.getServiceByUnitId('no-such/42', db.services, db.units);
      }, 'unit no-such/42 not found');
    });
  });

  it('can convert relation endpoints to their real names', function() {
    var services = new Y.ModelList();
    var args = [
      ['wordpress', {
        name: 'db',
        role: 'server'
      }],
      ['84882221$', {
        name: 'db',
        role: 'client'
      }],
      function() {}
    ];
    services.add([
      { id: 'foobar' },
      { id: '84882221$', displayName: '(mysql)' },
      { id: 'wordpress', displayName: 'wordpress' }
    ]);
    var services = changesUtils.getRealRelationEndpointNames(args, services);
    assert.deepEqual(services, ['mysql', 'wordpress']);
  });

  it('can generate descriptions for any change type', function() {
    addEntities(db);
    var tests = [{
      icon: 'django.svg',
      msg: ' cs:trusty/django-1 will be added to the controller.',
      change: {
        command: {
          method: '_addCharm',
          args: ['cs:trusty/django-1', 'cookies are better'],
          options: {applicationId: 'django'}
        }
      }
    }, {
      icon: 'django.svg',
      msg: ' django resources will be added.',
      change: {
        command: {
          method: '_addPendingResources',
          args: [{applicationName: 'django', charmURL: 'cs:trusty/django-1'}]
        }
      }
    }, {
      icon: 'django.svg',
      msg: ' django will be added to the model.',
      change: {
        command: {
          method: '_deploy',
          args: ['cs:trusty/django-1', 'django'],
          options: {modelId: 'django'}
        }
      },
      time: '12:34 PM'
    }, {
      icon: 'changes-units-added',
      msg: ' 1 django unit will be added.',
      change: {
        command: {
          method: '_add_unit',
          args: ['django', 1],
          options: {modelId: 'django/0'}
        }
      }
    }, {
      icon: 'changes-units-removed',
      msg: '1 unit will be removed from foo',
      change: {
        command: {
          method: '_remove_units',
          args: [['foo/0']]
        }
      }
    }, {
      // Note that this case is never used in production code.
      // We always add a single unit to a service.
      icon: 'changes-units-added',
      msg: ' 2 django units will be added.',
      change: {
        command: {
          method: '_add_unit',
          args: ['django', 2],
          options: {modelId: 'django/0'}
        }
      }
    }, {
      icon: 'changes-relation-added',
      msg: 'bar relation will be added between foo and baz.',
      change: {
        command: {
          method: '_add_relation',
          args: [
            ['foo', { name: 'bar' }],
            ['baz']
          ]
        }
      }
    }, {
      icon: 'changes-container-created',
      msg: '1 container will be added.',
      change: {
        command: {
          method: '_addMachines',
          args: [[{ parentId: 1 }]]
        }
      }
    }, {
      icon: 'changes-container-created',
      msg: '2 containers will be added.',
      change: {
        command: {
          method: '_addMachines',
          args: [[{ parentId: 1 }, { parentId: 1 }]]
        }
      }
    }, {
      icon: 'changes-machine-created',
      msg: '1 machine will be added.',
      change: {
        command: {
          method: '_addMachines',
          args: [[{}]]
        }
      }
    }, {
      icon: 'changes-machine-created',
      msg: '2 machines will be added.',
      change: {
        command: {
          method: '_addMachines',
          args: [[{}, {}]]
        }
      }
    }, {
      icon: 'changes-config-changed',
      msg: 'Configuration values will be changed for django.',
      change: {
        command: {
          method: '_set_config',
          args: ['django']
        }
      }
    }, {
      icon: 'changes-unknown',
      msg: 'An unknown change has been made to this model via the CLI.',
      change: {
        command: {
          method: '_anUnknownMethod'
        }
      }
    }];
    // This method needs to be stubbed out for the add relation path.
    var endpointNames = sinon.stub(
      changesUtils, 'getRealRelationEndpointNames').returns(['foo', 'baz']);
    this._cleanups.push(endpointNames.restore);
    tests.forEach(function(test) {
      var change = changesUtils.generateChangeDescription(
        db.services, db.units, test.change, true);
      assert.equal(change.icon, test.icon);
      assert.equal(change.description, test.msg);
      if (test.timestamp) {
        assert.equal(change.time, test.time);
      } else {
        assert.equal(change.time, '-');
      }
    });
  });

  it('can generate descriptions for all the changes in the ecs', function() {
    var stubDescription = sinon.stub(
      changesUtils,
      'generateChangeDescription');
    this._cleanups.push(stubDescription.restore);
    ecs.changeSet = { foo: { index: 0 }, bar: { index: 0 } };
    changesUtils.generateAllChangeDescriptions(
      db.services, db.units, ecs.changeSet);
    assert.equal(stubDescription.callCount, 2);
  });

  it('can get the counts of each type of ecs change', function() {
    var changeSet = {
      one: {command: {method: 'addMachine'}},
      two: {command: {method: 'addUnit'}},
      three: {command: {method: 'addMachine'}}
    };
    assert.deepEqual(changesUtils.getChangeCounts(changeSet), {
      addMachine: 2,
      addUnit: 1,
      total: 3
    });
  });

  it('can group the ecs change by type', function() {
    var changeSet = {
      one: {command: {method: 'addMachine'}},
      two: {command: {method: 'addUnit'}},
      three: {command: {method: 'addMachine'}}
    };
    assert.deepEqual(changesUtils.getGroupedChanges(changeSet), {
      addUnit: {
        two: {command: {method: 'addUnit'}}
      },
      addMachine: {
        one: {command: {method: 'addMachine'}},
        three: {command: {method: 'addMachine'}}
      }
    });
  });

  it('can filter the changeset by parent', function() {
    var changeSet = {
      one: {parents: ['parent1']},
      two: {parents: ['parent2']},
      three: {parents: ['parent1', 'parent2']}
    };
    assert.deepEqual(changesUtils.filterByParent(changeSet, 'parent1'), {
      one: {parents: ['parent1']},
      three: {parents: ['parent1', 'parent2']}
    });
  });

  describe('sortDescriptionsByApplication', function() {
    const getServiceById = sinon.stub();
    const descriptionsJSON = '[{"id":"addPendingResources-458","icon":"https://api.jujucharms.com/charmstore/v5/~containers/easyrsa-12/icon.svg","description":" easyrsa resources will be added.","time":"8:49 am"},{"id":"service-220","icon":"https://api.jujucharms.com/charmstore/v5/~containers/kubernetes-master-35/icon.svg","description":" kubernetes-master will be added to the model.","time":"8:49 am"},{"id":"expose-102","icon":"exposed_16","description":"kubernetes-master will be exposed","time":"8:49 am"},{"id":"addRelation-83","icon":"changes-relation-added","description":"kube-api-endpoint relation will be added between kubernetes-master and kubernetes-worker.","time":"8:49 am"},{"id":"addUnits-256","icon":"changes-units-added","description":" 1 easyrsa unit will be added.","time":"8:49 am"}]'; // eslint-disable-line max-len

    it('sorts additive descriptions by application', function() {
      const descriptions = JSON.parse(descriptionsJSON);
      const changeSet = {
        'addPendingResources-458': {
          command: {
            method: '_addPendingResources',
            args: [{applicationName: 'easyrsa'}]}},
        'service-220': {
          command: {
            method: '_deploy',
            args: [{applicationName: 'kubernetes-master'}]}},
        'expose-102': {
          command: {
            method: '_expose',
            args: ['kubernetes-master']}},
        'addRelation-83': {
          command: {
            method: '_add_relation',
            args: [['kubernetes-worker'], ['kubernetes-master']]}},
        'addUnits-256': {
          command: {
            method: '_add_unit',
            args: ['easyrsa']}}
      };
      const sorted = changesUtils.sortDescriptionsByApplication(
        getServiceById, changeSet, descriptions);
      const expected = {
        easyrsa: [{
          id: 'addPendingResources-458',
          icon: 'https://api.jujucharms.com/charmstore/v5/~containers/easyrsa-12/icon.svg', // eslint-disable-line max-len
          description: ' easyrsa resources will be added.',
          time: '8:49 am'
        }, {
          id: 'addUnits-256',
          icon: 'changes-units-added',
          description: ' 1 easyrsa unit will be added.',
          time: '8:49 am'
        }],
        'kubernetes-master': [{
          id: 'service-220',
          icon: 'https://api.jujucharms.com/charmstore/v5/~containers/kubernetes-master-35/icon.svg', // eslint-disable-line max-len
          description: ' kubernetes-master will be added to the model.',
          time: '8:49 am'
        }, {
          id: 'expose-102',
          icon: 'exposed_16',
          description: 'kubernetes-master will be exposed',
          time: '8:49 am'
        }, {
          id: 'addRelation-83',
          icon: 'changes-relation-added',
          description: 'kube-api-endpoint relation will be added between kubernetes-master and kubernetes-worker.', // eslint-disable-line max-len
          time: '8:49 am'
        }],
        'kubernetes-worker': [{
          id: 'addRelation-83',
          icon: 'changes-relation-added',
          description: 'kube-api-endpoint relation will be added between kubernetes-master and kubernetes-worker.', //eslint-disable-line max-len
          time: '8:49 am'
        }]
      };
      assert.deepEqual(sorted, expected);
    });

    it('skips methods in the blacklist', function() {
      const descriptions = [
        {id: 'addCharm-123'},
        {id: 'addMachines-123'},
        {id: 'addSSHKeys-123'},
        {id: 'importSSHKeys-123'},
        {id: 'destroyMachines-123'}
      ];
      const changeSet = {};
      const sorted = changesUtils.sortDescriptionsByApplication(
        getServiceById, changeSet, descriptions);
      assert.deepEqual(sorted, {});
    });

    it('does not error for removal commands', function() {
      const descriptions = [
        JSON.parse('{"id":"removeUnit-376","icon":"changes-units-removed","description":"1 unit will be removed from elasticsearch","time":"3:18 pm"}'), // eslint-disable-line max-len
        JSON.parse('{"id":"removeRelation-281","icon":"changes-relation-removed","description":"rest relation will be removed between kibana and elasticsearch.","time":"3:21 pm"}') // eslint-disable-line max-len
      ];

      const changeSet = {
        'removeUnit-376': {
          command: {
            method: '_remove_units',
            args: [['elasticsearch/0']]}},
        'removeRelation-281': {
          command: {
            method: '_remove_relation',
            args: [['kibana'],['elasticsearch']]
          }
        }
      };
      const sorted = changesUtils.sortDescriptionsByApplication(
        getServiceById, changeSet, descriptions);
      const expected = {
        elasticsearch: [{
          id: 'removeUnit-376',
          icon: 'changes-units-removed',
          description: '1 unit will be removed from elasticsearch',
          time: '3:18 pm'
        }, {
          id: 'removeRelation-281',
          icon: 'changes-relation-removed',
          description: 'rest relation will be removed between kibana and elasticsearch.', // eslint-disable-line max-len
          time: '3:21 pm'
        }],
        kibana: [{
          id: 'removeRelation-281',
          icon: 'changes-relation-removed',
          description: 'rest relation will be removed between kibana and elasticsearch.', // eslint-disable-line max-len
          time: '3:21 pm'
        }]
      };
      assert.deepEqual(sorted, expected);
    });
  });
});
<|endoftext|>


<commit_before>module.exports = function(mongoose) {
    var schema = new mongoose.Schema({
        text:String,
        app:{type:mongoose.Schema.Types.ObjectId,ref:"apps"},
        user:{type:mongoose.Schema.Types.ObjectId,ref:"users"},
        files:[{type:mongoose.Schema.Types.ObjectId,ref:"files"}],
        favoriteCount:{type:Number,default:0},
        replyTo:{type:mongoose.Schema.Types.ObjectId,ref:"posts"}
    },{
        timestamps:true
    })
    schema.methods.toResponseObject = async function (token) {
        var obj = this.toObject()
        obj.id = this._id
        obj._id = undefined
        obj.__v = undefined
        if (this.user.toResponseObject) obj.user=await this.user.toResponseObject(token)
        else {
            this.user = await mongoose.model("users").findById(this.user)
            if (this.user) obj.user = await this.user.toResponseObject(token)
        }
        if (this.app && this.app.toResponseObject) {
            obj.app=await this.app.toResponseObject(token)
            delete obj.app.appKey
            delete obj.app.appSecret
        }
        if (this.files && this.files.length) {
            for (let i=0;i<this.files.length;i++) {
                if (!this.files[i].toResponseObject) {
                    obj.files = []
                    break
                }
                obj.files[i] = await this.files[i].toResponseObject(token)
            }
        }
        if (token) {
            obj.isFavorited = !!(await mongoose.model("favorites").findOne({user:token.user.id,post:this.id}))
        }
        if (this.replyTo && this.replyTo.toResponseObject) obj.replyTo = await this.replyTo.toResponseObject()
        obj.html = obj.text
        obj.html = obj.html.split('&').join("&")
        obj.html = obj.html.split("<").join("<")
        obj.html = obj.html.split(">").join(">")
        // obj.html = obj.html.split('"').join(""")
        obj.html = obj.html.split("\n").join("
")
        obj.html = obj.html.replace(/(^| |\u3000)@([A-Za-z0-9_]+)/g,'$1@$2')
        obj.html = obj.html.replace(/(https?:\/\/[a-zA-Z0-9-\.]+(:[0-9]+)?(\/?[a-zA-Z0-9-\._~\!#$&'\(\)\*\+,\/:;=\?@\[\]]*))/g,'$1')
        obj.html = obj.html.replace(/moz:\/\/a/,'moz://a')
        return obj
    }
    return mongoose.model("posts",schema)
}
<commit_msg>fix moz://a<commit_after>module.exports = function(mongoose) {
    var schema = new mongoose.Schema({
        text:String,
        app:{type:mongoose.Schema.Types.ObjectId,ref:"apps"},
        user:{type:mongoose.Schema.Types.ObjectId,ref:"users"},
        files:[{type:mongoose.Schema.Types.ObjectId,ref:"files"}],
        favoriteCount:{type:Number,default:0},
        replyTo:{type:mongoose.Schema.Types.ObjectId,ref:"posts"}
    },{
        timestamps:true
    })
    schema.methods.toResponseObject = async function (token) {
        var obj = this.toObject()
        obj.id = this._id
        obj._id = undefined
        obj.__v = undefined
        if (this.user.toResponseObject) obj.user=await this.user.toResponseObject(token)
        else {
            this.user = await mongoose.model("users").findById(this.user)
            if (this.user) obj.user = await this.user.toResponseObject(token)
        }
        if (this.app && this.app.toResponseObject) {
            obj.app=await this.app.toResponseObject(token)
            delete obj.app.appKey
            delete obj.app.appSecret
        }
        if (this.files && this.files.length) {
            for (let i=0;i<this.files.length;i++) {
                if (!this.files[i].toResponseObject) {
                    obj.files = []
                    break
                }
                obj.files[i] = await this.files[i].toResponseObject(token)
            }
        }
        if (token) {
            obj.isFavorited = !!(await mongoose.model("favorites").findOne({user:token.user.id,post:this.id}))
        }
        if (this.replyTo && this.replyTo.toResponseObject) obj.replyTo = await this.replyTo.toResponseObject()
        obj.html = obj.text
        obj.html = obj.html.split('&').join("&")
        obj.html = obj.html.split("<").join("<")
        obj.html = obj.html.split(">").join(">")
        // obj.html = obj.html.split('"').join(""")
        obj.html = obj.html.split("\n").join("
")
        obj.html = obj.html.replace(/(^| |\u3000)@([A-Za-z0-9_]+)/g,'$1@$2')
        obj.html = obj.html.replace(/(https?:\/\/[a-zA-Z0-9-\.]+(:[0-9]+)?(\/?[a-zA-Z0-9-\._~\!#$&'\(\)\*\+,\/:;=\?@\[\]]*))/g,'$1')
        obj.html = obj.html.replace(/moz:\/\/a/,'moz://a')
        return obj
    }
    return mongoose.model("posts",schema)
}
<|endoftext|>


<commit_before>const pgp = require('pg-promise')()
const db = pgp({database: 'booky'})

const getAllBooks = (page = 1) => {
  const offset = (page-1) * 10
  const sql =`
  SELECT
    *
  FROM
    books
  `
  const variables = [] // [offset]
  return db.manyOrNone(sql, variables).then(addAuthorsToBooks).then(addGenresToBooks)
}

const getBookById = (id) => {
  const sql =
    `SELECT *
    FROM
      books
    WHERE
      id=${id}`
  const variables = [id]
  return db.oneOrNone(sql, variables)
}

const getBookByIdWithAuthors = (id) => {
  return Promise.all([
    getBookById(id),
    getAuthorsByBookId(id)
  ]).then(details => {
    const book = details[0]
    book.authors = details[1]
    return book
  })
}

const getBookByIdWithGenres = (id) => {
  return Promise.all([
      getBookById(id),
      getGenresByBookId(id)
    ]).then(details => {
      const book = details[0]
      book.genres = details[1]
      return book
    })
}

// const getAuthorsByBookId = (id) => {
//   const sql = `
//     SELECT *
//      FROM
//       authors
//      JOIN
//       book_authors
//      ON
//       book_authors.author_id=author_id
//      WHERE
//       book_authors.book_id=$1`
//   const variables = [id]
//   return db.manyOrNone(sql, variables)
// }

const getAuthorsByBookId = (id) => {
  const sql = `
  SELECT *
   FROM
    authors AS a
   JOIN
    book_authors
   ON
    book_authors.author_id=a.id
   WHERE
    book_authors.book_id= ${id}`
  const variables = [id]
  return db.manyOrNone(sql, variables)
}

const getGenresByBookId = (id) => {
  const sql = `
  SELECT *
   FROM
    genres AS g
   JOIN
    book_genres
   ON
    book_genres.genre_id=g.id
   WHERE
    book_genres.book_id=${id}`
  const variables = [id]
  return db.manyOrNone(sql, variables)
}

const findBooks = (query, page = 1) => {
  const offset = (page-1) * 10
  const sql = `
    SELECT DISTINCT
      books.*
    FROM
      books
    JOIN
      book_authors
    ON
      book_authors.book_id = books.id
    JOIN
      authors
    ON
      book_authors.author_id = authors.id
    JOIN
      book_genres
    ON
      book_genres.book_id = books.id
    JOIN
      genres
    ON
      book_genres.genre_id = genres.id
    WHERE
      LOWER(books.title) LIKE $1
    OR
      LOWER(books.description) LIKE $1
    OR
      LOWER(authors.name) LIKE $1
    OR
      LOWER(genres.name) LIKE $1
    LIMIT
      10
    OFFSET
      $2
  `
  const variables = [
    '%'+query.replace(/\s+/,'%').toLowerCase()+'%',
    offset,
  ]
  return db.manyOrNone(sql, variables).then(addAuthorsToBooks).then(addGenresToBooks)
}

const addAuthorsToBooks = books => {
  return getAuthorsForBooks(books).then(authors => {
    books.forEach(book => {
      book.authors = authors.filter(author =>
      author.book_id === book.id
      )
    })
    return books
  })
}

const addGenresToBooks = books => {
  return getGenresForBooks(books).then(genres => {
    books.forEach(book => {
      book.genres = genres.filter(genre =>
        genre.book_id === book.id
      )
    })
    return books
  })
}

const getAuthorsForBooks = (books) => {
  if (books.length === 0) return Promise.resolve( [])
  const bookIds = books.map(book => book.id)
  const sql = `
    SELECT
      authors.*,
      book_authors.book_id
    FROM
      authors
    JOIN
      book_authors
    ON
      book_authors.author_id = authors.id
    WHERE
      authors.id IN ($1:csv)`

  return db.manyOrNone(sql, [bookIds])
}

const getGenresForBooks = (books) => {
  if (books.length === 0) return Promise.resolve( [])
  const bookIds = books.map(book => book.id)
  const sql = `
    SELECT
      genres.*,
      book_genres.book_id
    FROM
      genres
    JOIN
      book_genres
    ON
      book_genres.genre_id = genres.id
    WHERE
      genres.id IN ($1:csv)
  `

  return db.manyOrNone(sql, [bookIds])
}

const createBook = (title, author, genre, description, image) => {
  console.log('hello', [title, author, genre, description, image])

  const sql = `
    INSERT INTO
      books (title, description, image_url)
    VALUES
      ($1, $2, $3)
    RETURNING
      *
  `

  const variables = [
    title,
    description,
    image
  ]
  // const insertAuthorQuery = createAuthor(author)
  // const insertGenreQuery = createGenre(genre)
  // const insertBookQuery = db.one(sql, variables)
  return Promise.all([
    createAuthor(author).catch(error => {console.log('A', error); throw error}),
    createGenre(genre).catch(error => {console.log('B', error); throw error}),
    db.one(sql, variables).catch(error => {console.log('C', error); throw error})
  ])
    .then(([author, genre, book]) => {
      return Promise.all([
        associateBookWithAuthor(book, author),
        associateBookWithGenre(book, genre),
      ]).then(() => book)
    })
    .catch(error => {console.log('E', error); throw error})
}

const associateBookWithAuthor = (book, author) => {
  const sql = `
    INSERT INTO
      book_authors(book_id, author_id)
    VALUES
      ($1, $2)
  `
  return db.any(sql, [book.id, author.id])
}

const associateBookWithGenre = (book, genre) => {
  const sql = `
    INSERT INTO
      book_genres(book_id, genre_id)
    VALUES
      ($1, $2)
  `
  return db.any(sql, [book.id, genre.id])
}

//const associateBookAuthorAndGenre = (book, author, genre) => {
  // const sql = `
  //   BEGIN TRANSACTION;
  //   INSERT INTO
  //     book_authors(book_id, author_id)
  //   VALUES
  //     ($1, $2)
  //   INSERT INTO
  //       book_genres(book_id, genre_id)
  //   VALUES
  //     (${book}, ${genre});
  //   COMMIT
  // `
  // const variables = [book.id, author.id, genre.id]
  // return db.none(sql, variables)
//}

const associateBookAndGenre = (book, genre) => {
  const sql = `
    INSERT INTO
      book_genres(book_id, genre_id)
    VALUES
      ($1, $2)
  `
  const variables = [book.id, genre.id]
  return db.none(sql, variables)
}

const createAuthor = (authorName) => {
  const sql = `
    INSERT INTO
      authors (name)
    VALUES
      ($1)
    RETURNING
      *
  `
  const variables = [authorName]
  return db.one(sql, variables)
}

const createGenre = (genreName) => {
  const sql = `
    INSERT INTO
      genres (name)
    VALUES
      ($1)
    RETURNING
      *
  `
  const variables = [genreName]
  return db.one(sql, variables)
}

const deleteBook = (bookIds) => {
  const sql = `
    DELETE FROM
      books
    WHERE
      id=${id}
  `
  const variables = [bookIds]
  return db.none(sql, variables)
}

const getAuthorForBookId = (bookId) => {
  const sql = `
    SELECT
      *
    FROM
      authors
    JOIN
      book_authors
    ON authors.id = book_authors.author_id
    WHERE
      book_authors.book_id = ${bookId}
  `
  return db.any(sql, [bookId])
}
  const getGenreForBookId = (bookId) => {
  const sql = `
  SELECT
      *
  FROM
    genres
  JOIN
    book_genres
  ON
    genres.id = book_genres.genre_id
  WHERE
    book_genres.book_id = ${bookId}
  `
  return db.any(sql, [bookId])
}

const getBookWithAuthorAndGenres = (bookId) => {
  return Promise.all([
    getBookById(bookId),
    getAuthorForBookId(bookId),
    getGenreForBookId(bookId),
  ]).then(([book, authors, genres]) => {
    book.authors = authors
    book.genres = genres
    // const bookInfo = {
    //   book,
    //   authors,
    //   genres
    // }
    return book
  })

}

module.exports = {
  getAllBooks,
  getBookById,
  getBookWithAuthorAndGenres,
  getGenreForBookId,
  getAuthorForBookId,
  getGenresByBookId,
  createBook,
  createGenre,
  createAuthor,
  findBooks
}
<commit_msg>fixed pagination issue<commit_after>const pgp = require('pg-promise')()
const db = pgp({database: 'booky'})

const getAllBooks = ( page = 1 ) => {
  const offset = ( page - 1 ) * 10
  const sql =`
  SELECT
    *
  FROM
    books
  LIMIT 10
  OFFSET $1
  `
  const variables = [offset]
  return db.manyOrNone( sql, variables ).then( addAuthorsToBooks ).then( addGenresToBooks )
}

const getBookById = ( id ) => {
  const sql =
    `SELECT
      *
     FROM
      books
    WHERE
      id=${id}`
  const variables = [ id ]
  return db.oneOrNone( sql, variables )
}

const getBookByIdWithAuthors = ( id ) => {
  return Promise.all([
    getBookById( id ),
    getAuthorsByBookId( id )
  ]).then(details => {
    const book = details[ 0 ]
    book.authors = details[ 1 ]
    return book
  })
}

const getBookByIdWithGenres = ( id ) => {
  return Promise.all([
      getBookById( id ),
      getGenresByBookId( id )
    ]).then(details => {
      const book = details[ 0 ]
      book.genres = details[ 1 ]
      return book
    })
}

// const getAuthorsByBookId = (id) => {
//   const sql = `
//     SELECT *
//      FROM
//       authors
//      JOIN
//       book_authors
//      ON
//       book_authors.author_id=author_id
//      WHERE
//       book_authors.book_id=$1`
//   const variables = [id]
//   return db.manyOrNone(sql, variables)
// }

const getAuthorsByBookId = ( id ) => {
  const sql = `
  SELECT
    *
  FROM
    authors AS a
  JOIN
    book_authors
  ON
    book_authors.author_id=a.id
  WHERE
    book_authors.book_id= ${id}`
  const variables = [id]
  return db.manyOrNone(sql, variables)
}

const getGenresByBookId = (id) => {
  const sql = `
  SELECT *
   FROM
    genres AS g
   JOIN
    book_genres
   ON
    book_genres.genre_id=g.id
   WHERE
    book_genres.book_id=${id}`
  const variables = [id]
  return db.manyOrNone(sql, variables)
}

const findBooks = (query, page = 1) => {
  const offset = (page-1) * 10
  const sql = `
    SELECT DISTINCT
      books.*
    FROM
      books
    JOIN
      book_authors
    ON
      book_authors.book_id = books.id
    JOIN
      authors
    ON
      book_authors.author_id = authors.id
    JOIN
      book_genres
    ON
      book_genres.book_id = books.id
    JOIN
      genres
    ON
      book_genres.genre_id = genres.id
    WHERE
      LOWER(books.title) LIKE $1
    OR
      LOWER(books.description) LIKE $1
    OR
      LOWER(authors.name) LIKE $1
    OR
      LOWER(genres.name) LIKE $1
    LIMIT
      10
    OFFSET
      $2
  `
  const variables = [
    '%'+query.replace(/\s+/,'%').toLowerCase()+'%',
    offset,
  ]
  return db.manyOrNone(sql, variables).then(addAuthorsToBooks).then(addGenresToBooks)
}

const addAuthorsToBooks = books => {
  return getAuthorsForBooks(books).then(authors => {
    books.forEach(book => {
      book.authors = authors.filter(author =>
      author.book_id === book.id
      )
    })
    return books
  })
}

const addGenresToBooks = books => {
  return getGenresForBooks(books).then(genres => {
    books.forEach(book => {
      book.genres = genres.filter(genre =>
        genre.book_id === book.id
      )
    })
    return books
  })
}

const getAuthorsForBooks = (books) => {
  if (books.length === 0) return Promise.resolve( [])
  const bookIds = books.map(book => book.id)
  const sql = `
    SELECT
      authors.*,
      book_authors.book_id
    FROM
      authors
    JOIN
      book_authors
    ON
      book_authors.author_id = authors.id
    WHERE
      authors.id IN ($1:csv)`

  return db.manyOrNone(sql, [bookIds])
}

const getGenresForBooks = (books) => {
  if (books.length === 0) return Promise.resolve( [])
  const bookIds = books.map(book => book.id)
  const sql = `
    SELECT
      genres.*,
      book_genres.book_id
    FROM
      genres
    JOIN
      book_genres
    ON
      book_genres.genre_id = genres.id
    WHERE
      genres.id IN ($1:csv)
  `

  return db.manyOrNone(sql, [bookIds])
}

const createBook = (title, author,genre,
  description, image) => {

  const sql = `
    INSERT INTO
      books (title, description, image_url)
    VALUES
      ($1, $2, $3)
    RETURNING
      *
  `
  const variables = [
    title,
    description,
    image
  ]
  return Promise.all([
    createAuthor(author).catch(error => {console.log('A', error); throw error}),
    createGenre(genre).catch(error => {console.log('B', error); throw error}),
    db.one(sql, variables).catch(error => {console.log('C', error); throw error})
  ])
    .then(([author, genre, book]) => {
      return Promise.all([
        associateBookWithAuthor(book, author),
        associateBookWithGenre(book, genre),
      ]).then(() => book)
    })
    .catch(error => {console.log('E', error); throw error})
}

const associateBookWithAuthor = (book, author) => {
  const sql = `
    INSERT INTO
      book_authors(book_id, author_id)
    VALUES
      ($1, $2)
  `
  return db.any(sql, [book.id, author.id])
}

const associateBookWithGenre = (book, genre) => {
  const sql = `
    INSERT INTO
      book_genres(book_id, genre_id)
    VALUES
      ($1, $2)
  `
  return db.any(sql, [book.id, genre.id])
}

//const associateBookAuthorAndGenre = (book, author, genre) => {
  // const sql = `
  //   BEGIN TRANSACTION;
  //   INSERT INTO
  //     book_authors(book_id, author_id)
  //   VALUES
  //     ($1, $2)
  //   INSERT INTO
  //       book_genres(book_id, genre_id)
  //   VALUES
  //     (${book}, ${genre});
  //   COMMIT
  // `
  // const variables = [book.id, author.id, genre.id]
  // return db.none(sql, variables)
//}

const associateBookAndGenre = (book, genre) => {
  const sql = `
    INSERT INTO
      book_genres(book_id, genre_id)
    VALUES
      ($1, $2)
  `
  const variables = [book.id, genre.id]
  return db.none(sql, variables)
}

const createAuthor = (authorName) => {
  const sql = `
    INSERT INTO
      authors (name)
    VALUES
      ($1)
    RETURNING
      *
  `
  const variables = [authorName]
  return db.one(sql, variables)
}

const createGenre = (genreName) => {
  const sql = `
    INSERT INTO
      genres (name)
    VALUES
      ($1)
    RETURNING
      *
  `
  const variables = [genreName]
  return db.one(sql, variables)
}

const deleteBook = (bookIds) => {
  const sql = `
    DELETE FROM
      books
    WHERE
      id=${id}
  `
  const variables = [bookIds]
  return db.none(sql, variables)
}

const getAuthorForBookId = (bookId) => {
  const sql = `
    SELECT
      *
    FROM
      authors
    JOIN
      book_authors
    ON authors.id = book_authors.author_id
    WHERE
      book_authors.book_id = ${bookId}
  `
  return db.any(sql, [bookId])
}
  const getGenreForBookId = (bookId) => {
  const sql = `
  SELECT
      *
  FROM
    genres
  JOIN
    book_genres
  ON
    genres.id = book_genres.genre_id
  WHERE
    book_genres.book_id = ${bookId}
  `
  return db.any(sql, [bookId])
}

const getBookWithAuthorAndGenres = (bookId) => {
  return Promise.all([
    getBookById(bookId),
    getAuthorForBookId(bookId),
    getGenreForBookId(bookId),
  ]).then(([book, authors, genres]) => {
    book.authors = authors
    book.genres = genres
    // const bookInfo = {
    //   book,
    //   authors,
    //   genres
    // }
    return book
  })

}

module.exports = {
  getAllBooks,
  getBookById,
  getBookWithAuthorAndGenres,
  getGenreForBookId,
  getAuthorForBookId,
  getGenresByBookId,
  createBook,
  createGenre,
  createAuthor,
  findBooks
}
<|endoftext|>


<commit_before>(function(){
	"use strict";

	var html         = document.documentElement;
	var touchEnabled = "ontouchstart" in html;

	/** Event types */
	var START     = touchEnabled ? "touchstart"  : "mousedown";
	var MOVE      = touchEnabled ? "touchmove"   : "mousemove";
	var END       = touchEnabled ? "touchend"    : "mouseup";
	var CANCEL    = touchEnabled ? "touchcancel" : null;

	function Gesture(el, options){
		options           = options || {};
		var THIS          = this;
		var _tracking     = false;

		var startCallback = options.onStart;
		var moveCallback  = options.onMove;
		var endCallback   = options.onEnd;

		Object.defineProperties(THIS, {

			/** Whether the gesture's currently being made */
			tracking: {
				get: function(){ return _tracking },
				set: function(i){
					if((i = !!i) !== _tracking){
						_tracking = i;

						if(i){
							html.addEventListener(MOVE, onMove);
							html.addEventListener(END,  onEnd);

							CANCEL && html.addEventListener(CANCEL, onEnd);
							window.addEventListener("blur", onEnd);
							window.addEventListener("contextmenu", onEnd);
						}

						else{
							html.removeEventListener(MOVE, onMove);
							html.removeEventListener(END,  onEnd);

							CANCEL && html.removeEventListener(CANCEL, onEnd);
							window.removeEventListener("blur", onEnd);
							window.removeEventListener("contextmenu", onEnd);
						}
					}
				}
			}
		});

		/**
		 * Return the coordinates of an event instance.
		 *
		 * @param {Event} event
		 * @return {Array}
		 */
		function getCoords(event){

			/** Touch-enabled device */
			if(touches = event.touches){
				var touches;
				var length = touches.length;

				/** Use .changedTouches if .touches is empty (e.g., "touchend" events) */
				if(!length){
					touches = event.changedTouches;
					length  = touches.length;
				}

				var result = [];
				for(var t, i = 0; i < length; ++i){
					t = touches[i];
					result.push(t.pageX, t.pageY);
				}
				return result;
			}

			/** MouseEvent or something similar */
			else return [event.pageX, event.pageY];
		}

		function onMove(event){
			event.preventDefault();
			moveCallback && moveCallback.call(null, getCoords(event), event, THIS);
		};

		function onEnd(event){
			THIS.tracking = false;
			endCallback && endCallback.call(null, getCoords(event), event, THIS);
		};

		el.addEventListener(START, function(event){

			/** Don't do anything if the user right-clicked */
			if(event.button > 0) return;

			/** Allow an onStart callback to abort the gesture by returning false */
			if(startCallback && false === startCallback.call(null, getCoords(event), event, THIS))
				return;

			THIS.tracking = true;
			event.preventDefault();
		});
	}

	/** Export */
	window.Gesture = Gesture;
}());
<commit_msg>Allow onMove callbacks to terminate gestures<commit_after>(function(){
	"use strict";

	var html         = document.documentElement;
	var touchEnabled = "ontouchstart" in html;

	/** Event types */
	var START     = touchEnabled ? "touchstart"  : "mousedown";
	var MOVE      = touchEnabled ? "touchmove"   : "mousemove";
	var END       = touchEnabled ? "touchend"    : "mouseup";
	var CANCEL    = touchEnabled ? "touchcancel" : null;

	function Gesture(el, options){
		options           = options || {};
		var THIS          = this;
		var _tracking     = false;

		var startCallback = options.onStart;
		var moveCallback  = options.onMove;
		var endCallback   = options.onEnd;

		THIS.onStart      = startCallback;
		THIS.onMove       = moveCallback;
		THIS.onEnd        = endCallback;

		Object.defineProperties(THIS, {

			/** Whether the gesture's currently being made */
			tracking: {
				get: function(){ return _tracking },
				set: function(i){
					if((i = !!i) !== _tracking){
						_tracking = i;

						if(i){
							html.addEventListener(MOVE, onMove);
							html.addEventListener(END,  onEnd);

							CANCEL && html.addEventListener(CANCEL, onEnd);
							window.addEventListener("blur", onEnd);
							window.addEventListener("contextmenu", onEnd);
						}

						else{
							html.removeEventListener(MOVE, onMove);
							html.removeEventListener(END,  onEnd);

							CANCEL && html.removeEventListener(CANCEL, onEnd);
							window.removeEventListener("blur", onEnd);
							window.removeEventListener("contextmenu", onEnd);
						}
					}
				}
			}
		});

		/**
		 * Return the coordinates of an event instance.
		 *
		 * @param {Event} event
		 * @return {Array}
		 */
		function getCoords(event){

			/** Touch-enabled device */
			if(touches = event.touches){
				var touches;
				var length = touches.length;

				/** Use .changedTouches if .touches is empty (e.g., "touchend" events) */
				if(!length){
					touches = event.changedTouches;
					length  = touches.length;
				}

				var result = [];
				for(var t, i = 0; i < length; ++i){
					t = touches[i];
					result.push(t.pageX, t.pageY);
				}
				return result;
			}

			/** MouseEvent or something similar */
			else return [event.pageX, event.pageY];
		}

		function onMove(event){

			/** Allow an onMove callback to abort the entire gesture by returning false */
			if(moveCallback && false === moveCallback.call(null, getCoords(event), event, THIS)){
				THIS.tracking = false;
				return;
			}
			event.preventDefault();
		};

		function onEnd(event){
			THIS.tracking = false;
			endCallback && endCallback.call(null, getCoords(event), event, THIS);
		};

		el.addEventListener(START, function(event){

			/** Don't do anything if the user right-clicked */
			if(event.button > 0) return;

			/** Allow an onStart callback to abort the gesture by returning false */
			if(startCallback && false === startCallback.call(null, getCoords(event), event, THIS))
				return;

			THIS.tracking = true;
			event.preventDefault();
		});
	}

	/** Export */
	window.Gesture = Gesture;
}());
<|endoftext|>


<commit_before>import config from '../config';
import Raven from 'raven';
import path from 'path';
import { version } from '../../../app/package.json';
import logger from '../utils/logger';
require.resolve('raven');

const executable = path.basename(process.argv[1]);
var ravenInstance;

function sendSourceMaps(data){
	var stacktrace = data.exception && data.exception[0].stacktrace;
	if (stacktrace && stacktrace.frames) {
		stacktrace.frames.forEach(function(frame) {
			if (frame.filename.indexOf('/api/dist/') !== -1 && frame.filename.indexOf('/node_modules/') === -1) {
				frame.filename = `app:///${frame.filename.split('api/dist/')[1]}`;
			}
		});
	}
	return data;
}

let sentryOptions = {
	dsn: config.sentry.dsn,
	level: 'error',
	patchGlobal: config.env === 'production',
	environment: config.env,
	tags: { script: executable },
	dataCallback: sendSourceMaps,
	release: `v${version}`,
};

ravenInstance = Raven.config(sentryOptions.dsn, sentryOptions);

if (config.sentry.dsn) {
	ravenInstance.install();
}

function captureError(err, msg) {
	Raven.captureException(err);
}

exports.setupExpressRequestHandler = (app) => {
	if (config.sentry.dsn) {
		app.use(ravenInstance.requestHandler());
	}
};

exports.setupExpressErrorHandler = (app) => {
	if (config.sentry.dsn) {
		app.use(ravenInstance.errorHandler());
	}
	app.use(function (err, req, res, next) {
		var status =
			err.status ||
			err.statusCode ||
			err.status_code ||
			(err.output && err.output.statusCode) ||
			500;
		// skip anything not marked as an internal server error
		if (status < 500) return next(err);
		logger.error({err});
		return next(err);
	});
};

exports.Throw = () => {
	throw new Error('test');
};

exports.Raven = ravenInstance;
exports.CaptureError = captureError;
<commit_msg>giving up on sane error tracking on Node<commit_after>import config from '../config';
import Raven from 'raven';
import path from 'path';
import { version } from '../../../app/package.json';
import logger from '../utils/logger';
require.resolve('raven');

const executable = path.basename(process.argv[1]);
var ravenInstance;

function sendSourceMaps(data){
	var stacktrace = data.exception && data.exception[0].stacktrace;
	if (stacktrace && stacktrace.frames) {
		stacktrace.frames.forEach(function(frame) {
			if (frame.filename.indexOf('/api/dist/') !== -1 && frame.filename.indexOf('/node_modules/') === -1) {
				frame.filename = `app:///${frame.filename.split('api/dist/')[1]}`;
			}
		});
	}
	return data;
}

let sentryOptions = {
	dsn: config.sentry.dsn,
	level: 'error',
	patchGlobal: config.env === 'production',
	environment: config.env,
	tags: { script: executable },
	// dataCallback: sendSourceMaps,
	release: `v${version}`,
};

ravenInstance = Raven.config(sentryOptions.dsn, sentryOptions);

if (config.sentry.dsn) {
	ravenInstance.install();
}

function captureError(err, msg) {
	Raven.captureException(err);
}

exports.setupExpressRequestHandler = (app) => {
	if (config.sentry.dsn) {
		app.use(ravenInstance.requestHandler());
	}
};

exports.setupExpressErrorHandler = (app) => {
	if (config.sentry.dsn) {
		app.use(ravenInstance.errorHandler());
	}
	app.use(function (err, req, res, next) {
		var status =
			err.status ||
			err.statusCode ||
			err.status_code ||
			(err.output && err.output.statusCode) ||
			500;
		// skip anything not marked as an internal server error
		if (status < 500) return next(err);
		logger.error({err});
		return next(err);
	});
};

exports.Throw = () => {
	throw new Error('test');
};

exports.Raven = ravenInstance;
exports.CaptureError = captureError;
<|endoftext|>


<commit_before>/*
  ==============================================
  WELCOME TO BUILD YOUR OWN FUNCTIONS TDD STYLE
  ==============================================
  - In this section you will replicate a Javascript Array methods.
  - We have multiple tests that should be solved, make sure to go
    one by one and read the error messages to get clues.
  - To solve the tests you should read the errors and try to solve them
    by writing the function that has already been declared.
    You are very welcomed to read the tests to gain insight but
    DO NOT change them, only the function.
  - Bellow we provide a short description of the function as well as the expected
    syntax. Documentation is unbeatable, so you are encouraged to click the link
    and gain further insight.
  - Try mixing it up for added difficulty. Go low level and avoid using other
    array methods. Can it be done without for loops?
  - ONWARDS!

  Description:
  The indexOf() method returns the first index at which a given element
  can be found in the array, or -1 if it is not present.

  Syntax:
  indexOf(array, searchElement[, fromIndex])

  Link to docs https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/indexof
*/

////function indexOf(/*function parameters*/) {
function indexOf(array, searchElement, fromIndex) {
  ////
  return array.indexOf(searchElement, fromIndex);
  /*
    Your code goes here
  */
}

describe("Implementing the `indexOf()` array method", () => {
  describe("it's functionality", ()=>{
    it("returns the index of the searched element in the array", () => {
      const expected = 2;
      const actual = indexOf(["a", "b", "c"], "c");
      assert.equal(expected, actual);
    });
    it("returns -1 if the searched element is not found in the array", () => {
      const expected = -1;
      const actual = indexOf(["a", "b", "c"], "e");
      assert.equal(expected, actual);
    });
    it("begins looking for the searched element starting from the indexFrom value", () => {
      const expected = -1;
      const actual = indexOf(["a", "b", "c"], "a", 2);
      assert.equal(expected, actual);
    });
    it("searches the whole array if indexFrom is 0", () => {
      const expected = 2;
      const actual = indexOf(["a", "b", "c"], "c", 0);
      assert.equal(expected, actual);
    });
    it("searches the whole array if indexFrom has a negative value", () => {
      const expected = 1;
      const actual = indexOf(["a", "b", "c"], "b", -5);
      assert.equal(expected, actual);
    });
  });
  describe("it's parameters", ()=>{
    it("throws an error if the first argument is a number", () => {
      assert.throws(() => indexOf(47, 2), TypeError);
    });
    it("parses indexFrom if it's a string containing a number", () => {
      const expected = 1;
      const actual = indexOf(["a", "b", "c"], "b", "1");
      assert.equal(expected, actual);
    });
    it("searches the whole array if indexFrom can't be parsed into a number", () => {
      const expected = 2;
      const actual = indexOf(["a", "b", "c"], "c", "hello");
      assert.equal(expected, actual);
    });
    it("returns -1 if the indexFrom is greater than or equal to the array's length", () => {
      const expected = -1;
      const actual = indexOf(["a", "b", "c"], "a", 3);
      assert.equal(expected, actual);
    });
  });
});
<commit_msg>[es1][array API] swap expected and actual arguments<commit_after>/*
  ==============================================
  WELCOME TO BUILD YOUR OWN FUNCTIONS TDD STYLE
  ==============================================
  - In this section you will replicate a Javascript Array methods.
  - We have multiple tests that should be solved, make sure to go
    one by one and read the error messages to get clues.
  - To solve the tests you should read the errors and try to solve them
    by writing the function that has already been declared.
    You are very welcomed to read the tests to gain insight but
    DO NOT change them, only the function.
  - Bellow we provide a short description of the function as well as the expected
    syntax. Documentation is unbeatable, so you are encouraged to click the link
    and gain further insight.
  - Try mixing it up for added difficulty. Go low level and avoid using other
    array methods. Can it be done without for loops?
  - ONWARDS!

  Description:
  The indexOf() method returns the first index at which a given element
  can be found in the array, or -1 if it is not present.

  Syntax:
  indexOf(array, searchElement[, fromIndex])

  Link to docs https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/indexof
*/

////function indexOf(/*function parameters*/) {
function indexOf(array, searchElement, fromIndex) {
  ////
  return array.indexOf(searchElement, fromIndex);
  /*
    Your code goes here
  */
}

describe("Implementing the `indexOf()` array method", () => {
  describe("it's functionality", ()=>{
    it("returns the index of the searched element in the array", () => {
      const expected = 2;
      const actual = indexOf(["a", "b", "c"], "c");
      assert.equal(actual, expected);
    });
    it("returns -1 if the searched element is not found in the array", () => {
      const expected = -1;
      const actual = indexOf(["a", "b", "c"], "e");
      assert.equal(actual, expected);
    });
    it("begins looking for the searched element starting from the indexFrom value", () => {
      const expected = -1;
      const actual = indexOf(["a", "b", "c"], "a", 2);
      assert.equal(actual, expected);
    });
    it("searches the whole array if indexFrom is 0", () => {
      const expected = 2;
      const actual = indexOf(["a", "b", "c"], "c", 0);
      assert.equal(actual, expected);
    });
    it("searches the whole array if indexFrom has a negative value", () => {
      const expected = 1;
      const actual = indexOf(["a", "b", "c"], "b", -5);
      assert.equal(actual, expected);
    });
  });
  describe("it's parameters", ()=>{
    it("throws an error if the first argument is a number", () => {
      assert.throws(() => indexOf(47, 2), TypeError);
    });
    it("parses indexFrom if it's a string containing a number", () => {
      const expected = 1;
      const actual = indexOf(["a", "b", "c"], "b", "1");
      assert.equal(actual, expected);
    });
    it("searches the whole array if indexFrom can't be parsed into a number", () => {
      const expected = 2;
      const actual = indexOf(["a", "b", "c"], "c", "hello");
      assert.equal(actual, expected);
    });
    it("returns -1 if the indexFrom is greater than or equal to the array's length", () => {
      const expected = -1;
      const actual = indexOf(["a", "b", "c"], "a", 3);
      assert.equal(actual, expected);
    });
  });
});
<|endoftext|>