<commit_before>this.addEventListener("install", function (event) {
    event.waitUntil(
        caches.open('v14').then(function (cache) {
         return cache.addAll(['/html/',
          '/html/index.html',
          '/html/manifest.json',
          '/html/emergency.html',
          '/html/js/911.js',
          '/html/js/all_scr.js',
          '/html/js/app.js',
          '/html/js/bootstrap.min.js',
          '/html/js/emergency.js',
          '/html/js/wow.min.js',
          '/html/rs-plugin/js/jquery.themepunch.tools.min.js',
          '/html/rs-plugin/js/jquery.themepunch.revolution.min.js',
          '/html/rs-plugin/assets/timer.png',
          '/html/js/jquery.min.js',
          '/html/js/jquery.parallax-0.2-min.js',
          '/html/css/adaptive.css',
          '/html/css/animate.css',
          '/html/css/bootstrap.css',
          '/html/css/main.css',
          '/html/css/pushy.css',
          '/html/css/font-awesome.css',
          '/html/rs-plugin/css/settings.css',
          '/html/img/trapped_lady.jpg',
          '/html/img/screaming.jpg',
          '/html/img/location4.jpg',
          '/html/img/embrace3.jpg',
          '/html/img/start/bg.jpg',
          '/html/font/fontawesome-webfont.woff?v=4.3.0',
          '/html/font/fontawesome-webfont.woff2?v=4.3.0',
          '/html/rs-plugin/font/revicons.woff?5510888',
          '/html/rs-plugin/font/revicons.ttf?5510888',
          '/html/font/fontawesome-webfont.ttf?v=4.3.0',
          '/html/img/wmb_128x128.png',
          '/html/img/wmb_144x144.png',
          '/html/img/wmb_152x152.png',
          '/html/img/wmb_192x192.png',
          '/html/img/wmb_256x256.png'
          ]);
        })
        );
    });

this.addEventListener('activate', function(event) {
  var cacheWhitelist = ['v14'];

  event.waitUntil(
    caches.keys().then(function(keyList) {
      return Promise.all(keyList.map(function(key) {
        if (cacheWhitelist.indexOf(key) === -1) {
          return caches.delete(key);
        }
      }));
    })
  );
});
this.addEventListener('fetch', function (event) {
	event.respondWith(
    caches.match(event.request)
    );
});

// push notification begin here

this.addEventListener("push", event => {
   event.waitUntil(() => {
     if (event.data) {
      return Promise.resolve(event.data);
     }
     return fetch("demo_sse.php").then(response => response.json());
).then(data => {
 return this.registration.showNotification(title, {
    body : 'help me',
    icon : 'screaming.jpg',
    vibrate: [200, 100, 200, 100, 400],
    tag : 'request',
    actions : [
    { action : "track", title : "wacth", icon : "fa fa-thumb-up"},
    { action : "Ignore", title : "Ignore", icon : "fa fa-thumb-down"} ]
  });
});

// this is for the click event

this.addEventListener("notificationclick", event => {
event.waitUntil(() =>
   event.notification.close();
  )

});<commit_msg>offline cache<commit_after>/*this.addEventListener("install", function (event) {
    event.waitUntil(
        caches.open('v14').then(function (cache) {
         return cache.addAll(['/html/',
          '/html/index.html',
          '/html/manifest.json',
          '/html/emergency.html',
          '/html/js/911.js',
          '/html/js/all_scr.js',
          '/html/js/app.js',
          '/html/js/bootstrap.min.js',
          '/html/js/emergency.js',
          '/html/js/wow.min.js',
          '/html/rs-plugin/js/jquery.themepunch.tools.min.js',
          '/html/rs-plugin/js/jquery.themepunch.revolution.min.js',
          '/html/rs-plugin/assets/timer.png',
          '/html/js/jquery.min.js',
          '/html/js/jquery.parallax-0.2-min.js',
          '/html/css/adaptive.css',
          '/html/css/animate.css',
          '/html/css/bootstrap.css',
          '/html/css/main.css',
          '/html/css/pushy.css',
          '/html/css/font-awesome.css',
          '/html/rs-plugin/css/settings.css',
          '/html/img/trapped_lady.jpg',
          '/html/img/screaming.jpg',
          '/html/img/location4.jpg',
          '/html/img/embrace3.jpg',
          '/html/img/start/bg.jpg',
          '/html/font/fontawesome-webfont.woff?v=4.3.0',
          '/html/font/fontawesome-webfont.woff2?v=4.3.0',
          '/html/rs-plugin/font/revicons.woff?5510888',
          '/html/rs-plugin/font/revicons.ttf?5510888',
          '/html/font/fontawesome-webfont.ttf?v=4.3.0',
          '/html/img/wmb_128x128.png',
          '/html/img/wmb_144x144.png',
          '/html/img/wmb_152x152.png',
          '/html/img/wmb_192x192.png',
          '/html/img/wmb_256x256.png'
          ]);
        })
        );
    });

this.addEventListener('activate', function(event) {
  var cacheWhitelist = ['v14'];

  event.waitUntil(
    caches.keys().then(function(keyList) {
      return Promise.all(keyList.map(function(key) {
        if (cacheWhitelist.indexOf(key) === -1) {
          return caches.delete(key);
        }
      }));
    })
  );
});
this.addEventListener('fetch', function (event) {
	event.respondWith(
    caches.match(event.request)
    );
});

// push notification begin here

this.addEventListener("push", event => {
   event.waitUntil(() => {
     if (event.data) {
      return Promise.resolve(event.data);
     }
     return fetch("demo_sse.php").then(response => response.json());
).then(data => {
 return this.registration.showNotification(title, {
    body : 'help me',
    icon : 'screaming.jpg',
    vibrate: [200, 100, 200, 100, 400],
    tag : 'request',
    actions : [
    { action : "track", title : "wacth", icon : "fa fa-thumb-up"},
    { action : "Ignore", title : "Ignore", icon : "fa fa-thumb-down"} ]
  });
});*/

// this is for the click event

this.addEventListener("notificationclick", event => {
event.waitUntil(() =>
   event.notification.close();
  )

});<|endoftext|>


<commit_before>/**
* @license Apache-2.0
*
* Copyright (c) 2018 The Stdlib Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*    http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

'use strict';

// MODULES //

var tape = require( 'tape' );
var randu = require( '@stdlib/random/base/randu' );
var roundn = require( '@stdlib/math/base/special/roundn' );
var contains = require( '@stdlib/assert/contains' );
var discreteUniform = require( '@stdlib/random/base/discrete-uniform' );
var anova1 = require( './../lib' );

// TESTS //

tape( 'main export is a function', function test( t ) {
	t.ok( true, __filename );
	t.equal( typeof anova1, 'function', 'main export is a function' );
	t.end();
});

tape( 'the function throws an error if the `x` argument is not a numeric array', function test( t ) {
	var values;
	var i;

	values = [
		'5',
		5,
		[ 1, 2, '5' ],
		true,
		undefined,
		null,
		NaN,
		function noop() {},
		{}
	];

	for ( i = 0; i < values.length; i++ ) {
		t.throws( badValue( values[i] ), TypeError, 'throws an error when provided '+values[i] );
	}
	t.end();

	function badValue( value ) {
		return function badValue() {
			// Make the factor the same
			anova1( value, ['treatment A', 'control'] );
		};
	}
});

tape( 'the function throws an error if the `x` argument has a length smaller than 2', function test( t ) {
	var values;
	var i;

	values = [
		[],
		[5]
	];

	for ( i = 0; i < values.length; i++ ) {
		t.throws( badValue( values[i] ), Error, 'throws an error when provided '+values[i] );
	}
	t.end();

	function badValue( value ) {
		return function badValue() {
			anova1( value, ['treatment A', 'control'] );
		};
	}
});

tape( 'the function throws an error if the `factor` argument is not an array', function test( t ) {
	var values;
	var i;

	values = [
		5,
		true,
		NaN,
		null
	];

	for ( i = 0; i < values.length; i++ ) {
		t.throws( badValue( values[i] ), Error, 'throws an error when provided '+values[i] );
	}
	t.end();

	function badValue( value ) {
		return function badValue() {
			anova1( [1, 2, 3, 4, 5], value );
		};
	}
});

tape( 'the function throws an error if the `factor` argument has a length smaller than 2', function test( t ) {
	var values;
	var i;

	values = [
		[],
		[14],
		['treatment A']
	];

	for ( i = 0; i < values.length; i++ ) {
		t.throws( badValue( values[i] ), Error, 'throws an error when provided '+values[i] );
	}
	t.end();

	function badValue( value ) {
		return function badValue() {
			anova1( [1, 2, 3], value );
		};
	}
});

tape( 'the function throws an error if the `factor` argument has less than 2 unique elements', function test( t ) {
	var values;
	var i;

	values = [
		[ 'treatment A', 'treatment A', 'treatment A' ],
		[ 'treatment C', 'treatment C', 'treatment C', 'treatment C' ]
	];

	for ( i = 0; i < values.length; i++ ) {
		t.throws( badValue( values[i] ), Error, 'throws an error when provided '+values[i] );
	}
	t.end();

	function badValue( value ) {
		return function badValue() {
			anova1( [1, 2, 3], value );
		};
	}
});

tape( 'the function throws an error if the arguments have unequal lengths', function test( t ) {
	// Two unique arguments for x and factor
	var valuesX;
	var valuesY;
	var i;

	valuesX = [
		[ 1, 2, 3, 4, 5, 6, 7, 8 ],
		[ 5, 6, 7, 4 ],
		[ 1, 2, 3 ]
	];

	valuesY = [
		[ 'treatment A', 'treatment C', 'treatment A', 'treatment C' ],
		[ 'control', 'treatment C', 'treatment B' ],
		[ 'john', 'delta beta' ]
	];

	for ( i = 0; i < valuesX.length; i++ ) {
		t.throws( badValue( valuesX[i], valuesY[i] ), Error, 'throws an error when provided '+valuesX[i]+valuesY[i] );
	}
	t.end();

	function badValue( valueX, valueY ) {
		return function badValue() {
			anova1( valueX, valueY );
		};
	}
});

tape( 'the function produces expected values on simple inputs', function test( t ) {
	var out;
	var x;
	var f;

	x = [ 3, 4, 5, 6, 2, 5, 10, 12, 8, 10 ];
	f = [ 'treatment A', 'control', 'treatment B', 'treatment A', 'control', 'treatment B', 'treatment A', 'control', 'treatment B', 'treatment A' ];
	out = anova1( x, f );

	// Tested against R
	t.equal( out.treatment.df, 2 );
	t.equal( out.treatment.ss, 3.75 );
	t.equal( out.treatment.mss, 1.875 );
	t.equal( out.error.df, 7 );
	t.equal( out.error.ss, 96.75 );
	t.equal( roundn(out.error.mss, -4), 13.8214 );
	t.equal( roundn(out.statistic, -4), 0.1357 );
	t.equal( roundn(out.pValue, -4), 0.8754 );
	t.end();
});

tape( 'the `.print()` method allows printing a formatted output table', function test( t ) {
	var actual;
	var table;
	var group;
	var vals;
	var len;
	var i;

	len = 30;
	vals = new Array( len );
	group = new Array( len );
	for ( i = 0; i < len; i++ ) {
		group[ i ] = discreteUniform( 0, 3 );
		vals[ i ] = ( randu()*50.0 ) + ( 10.0*group[ i ] );
	}
	actual = anova1( group, vals );
	table = actual.print();

	t.equal( typeof table, 'string', 'returns a string' );
	t.end();
});

tape( 'the function returns an object with a `.print()` method that accepts a `numdigits` option to control the number of decimal digits displayed', function test( t ) {
	var actual;
	var table;
	var group;
	var vals;
	var len;
	var i;

	len = 30;
	vals = new Array( len );
	group = new Array( len );
	for ( i = 0; i < len; i++ ) {
		group[ i ] = discreteUniform( 0, 3 );
		vals[ i ] = ( randu()*50.0 ) + ( 10.0*group[ i ] );
	}
	actual = anova1( group, vals );
	table = actual.print({
		'numdigits': 6
	});
	t.equal( typeof table, 'string', 'returns a pretty-printed table' );
	t.end();
});

tape( 'the function returns an object with a `.print()` method that accepts a `decision` option to control whether the test result should be displayed', function test( t ) {
	var actual;
	var table;
	var group;
	var vals;
	var len;
	var i;

	len = 30;
	vals = new Array( len );
	group = new Array( len );
	for ( i = 0; i < len; i++ ) {
		group[ i ] = discreteUniform( 0, 3 );
		vals[ i ] = ( randu()*50.0 ) + ( 10.0*group[ i ] );
	}
	actual = anova1( group, vals );
	table = actual.print({
		'decision': false
	});
	t.equal( typeof table, 'string', 'returns a pretty-printed table' );
	t.equal( contains( table, 'Test Decision' ), false, 'table does not contain test decision' );
	t.end();
});

tape( 'the function returns an object with a `.print()` method that accepts an `options` object', function test( t ) {
	var table;
	var out;
	var f;
	var x;

	x = [ 3, 4, 5, 6, 2, 5, 10, 12, 8, 10 ];
	f = [ 'treatment A', 'control', 'treatment B', 'treatment A', 'control', 'treatment B', 'treatment A', 'control', 'treatment B', 'treatment A' ];
	out = anova1( x, f );
	table = out.print( {} );

	t.equal( typeof table, 'string', 'returns a pretty-printed table' );
	t.end();
});

tape( 'the function returns an object with a `.print()` method that throws an error if `options` is not a simple object', function test( t ) {
	var values;
	var out;
	var i;
	var x;
	var f;

	x = [ 3, 4, 5, 6, 2, 5, 10, 12, 8, 10 ];
	f = [ 'treatment A', 'control', 'treatment B', 'treatment A', 'control', 'treatment B', 'treatment A', 'control', 'treatment B', 'treatment A' ];
	out = anova1( x, f );

	values = [
		'abc',
		4,
		null,
		true,
		undefined,
		NaN,
		[],
		function noop() {}
	];

	for ( i = 0; i < values.length; i++ ) {
		t.throws( badValue( values[i] ), TypeError, 'throws an error when provided '+values[i] );
	}
	t.end();

	function badValue( value ) {
		return function badValue() {
			out.print( value );
		};
	}
});

tape( 'the function returns an object with a `.print()` method that throws an error if the `numdigits` option is not a positive integer', function test( t ) {
	var values;
	var out;
	var f;
	var i;
	var x;

	x = [ 3, 4, 5, 6, 2, 5, 10, 12, 8, 10 ];
	f = [ 'treatment A', 'control', 'treatment B', 'treatment A', 'control', 'treatment B', 'treatment A', 'control', 'treatment B', 'treatment A' ];
	out = anova1( x, f );

	values = [
		'abc',
		2.4,
		0.0,
		-1.0,
		null,
		true,
		undefined,
		NaN,
		[],
		{},
		function noop() {}
	];

	for ( i = 0; i < values.length; i++ ) {
		t.throws( badValue( values[i] ), TypeError, 'throws an error when provided '+values[i] );
	}
	t.end();

	function badValue( value ) {
		return function badValue() {
			out.print({
				'numdigits': value
			});
		};
	}
});

tape( 'the function returns an object with a `.print()` method that throws an error if the `decision` option is not a boolean primitive', function test( t ) {
	var values;
	var out;
	var f;
	var i;
	var x;

	x = [ 3, 4, 5, 6, 2, 5, 10, 12, 8, 10 ];
	f = [ 'treatment A', 'control', 'treatment B', 'treatment A', 'control', 'treatment B', 'treatment A', 'control', 'treatment B', 'treatment A' ];
	out = anova1( x, f );

	values = [
		'abc',
		2.4,
		0.0,
		-1.0,
		null,
		7.0,
		undefined,
		NaN,
		[],
		{},
		function noop() {}
	];

	for ( i = 0; i < values.length; i++ ) {
		t.throws( badValue( values[i] ), TypeError, 'throws an error when provided '+values[i] );
	}
	t.end();

	function badValue( value ) {
		return function badValue() {
			out.print({
				'decision': value
			});
		};
	}
});
<commit_msg>Fix typo and resolve style issues<commit_after>/**
* @license Apache-2.0
*
* Copyright (c) 2018 The Stdlib Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*    http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

'use strict';

// MODULES //

var tape = require( 'tape' );
var randu = require( '@stdlib/random/base/randu' );
var roundn = require( '@stdlib/math/base/special/roundn' );
var contains = require( '@stdlib/assert/contains' );
var discreteUniform = require( '@stdlib/random/base/discrete-uniform' );
var anova1 = require( './../lib' );

// TESTS //

tape( 'main export is a function', function test( t ) {
	t.ok( true, __filename );
	t.equal( typeof anova1, 'function', 'main export is a function' );
	t.end();
});

tape( 'the function throws an error if the `x` argument is not a numeric array', function test( t ) {
	var values;
	var i;

	values = [
		'5',
		5,
		[ 1, 2, '5' ],
		true,
		undefined,
		null,
		NaN,
		function noop() {},
		{}
	];

	for ( i = 0; i < values.length; i++ ) {
		t.throws( badValue( values[i] ), TypeError, 'throws an error when provided '+values[i] );
	}
	t.end();

	function badValue( value ) {
		return function badValue() {
			anova1( value, [ 'treatment A', 'control' ] );
		};
	}
});

tape( 'the function throws an error if the `x` argument has a length smaller than 2', function test( t ) {
	var values;
	var i;

	values = [
		[],
		[5]
	];

	for ( i = 0; i < values.length; i++ ) {
		t.throws( badValue( values[i] ), Error, 'throws an error when provided '+values[i] );
	}
	t.end();

	function badValue( value ) {
		return function badValue() {
			anova1( value, [ 'treatment A', 'control' ] );
		};
	}
});

tape( 'the function throws an error if the `factor` argument is not an array', function test( t ) {
	var values;
	var i;

	values = [
		5,
		true,
		NaN,
		null
	];

	for ( i = 0; i < values.length; i++ ) {
		t.throws( badValue( values[i] ), Error, 'throws an error when provided '+values[i] );
	}
	t.end();

	function badValue( value ) {
		return function badValue() {
			anova1( [1, 2, 3, 4, 5], value );
		};
	}
});

tape( 'the function throws an error if the `factor` argument has a length smaller than 2', function test( t ) {
	var values;
	var i;

	values = [
		[],
		[14],
		['treatment A']
	];

	for ( i = 0; i < values.length; i++ ) {
		t.throws( badValue( values[i] ), Error, 'throws an error when provided '+values[i] );
	}
	t.end();

	function badValue( value ) {
		return function badValue() {
			anova1( [1, 2, 3], value );
		};
	}
});

tape( 'the function throws an error if the `factor` argument has less than 2 unique elements', function test( t ) {
	var values;
	var i;

	values = [
		[ 'treatment A', 'treatment A', 'treatment A' ],
		[ 'treatment C', 'treatment C', 'treatment C', 'treatment C' ]
	];

	for ( i = 0; i < values.length; i++ ) {
		t.throws( badValue( values[i] ), Error, 'throws an error when provided '+values[i] );
	}
	t.end();

	function badValue( value ) {
		return function badValue() {
			anova1( [1, 2, 3], value );
		};
	}
});

tape( 'the function throws an error if the arguments have unequal lengths', function test( t ) {
	// Two unique arguments for x and factor
	var valuesX;
	var valuesY;
	var i;

	valuesX = [
		[ 1, 2, 3, 4, 5, 6, 7, 8 ],
		[ 5, 6, 7, 4 ],
		[ 1, 2, 3 ]
	];

	valuesY = [
		[ 'treatment A', 'treatment C', 'treatment A', 'treatment C' ],
		[ 'control', 'treatment C', 'treatment B' ],
		[ 'john', 'delta beta' ]
	];

	for ( i = 0; i < valuesX.length; i++ ) {
		t.throws( badValue( valuesX[i], valuesY[i] ), Error, 'throws an error when provided '+valuesX[i]+valuesY[i] );
	}
	t.end();

	function badValue( valueX, valueY ) {
		return function badValue() {
			anova1( valueX, valueY );
		};
	}
});

tape( 'the function produces expected values on simple inputs', function test( t ) {
	var out;
	var x;
	var f;

	x = [ 3, 4, 5, 6, 2, 5, 10, 12, 8, 10 ];
	f = [ 'treatment A', 'control', 'treatment B', 'treatment A', 'control', 'treatment B', 'treatment A', 'control', 'treatment B', 'treatment A' ];
	out = anova1( x, f );

	// Tested against R:
	t.equal( out.treatment.df, 2 );
	t.equal( out.treatment.ss, 3.75 );
	t.equal( out.treatment.mss, 1.875 );
	t.equal( out.error.df, 7 );
	t.equal( out.error.ss, 96.75 );
	t.equal( roundn(out.error.mss, -4), 13.8214 );
	t.equal( roundn(out.statistic, -4), 0.1357 );
	t.equal( roundn(out.pValue, -4), 0.8754 );
	t.end();
});

tape( 'the `.print()` method allows printing a formatted output table', function test( t ) {
	var actual;
	var table;
	var group;
	var vals;
	var len;
	var i;

	len = 30;
	vals = new Array( len );
	group = new Array( len );
	for ( i = 0; i < len; i++ ) {
		group[ i ] = discreteUniform( 0, 3 );
		vals[ i ] = ( randu()*50.0 ) + ( 10.0*group[ i ] );
	}
	actual = anova1( group, vals );
	table = actual.print();

	t.equal( typeof table, 'string', 'returns a string' );
	t.end();
});

tape( 'the function returns an object with a `.print()` method that accepts a `numdigits` option to control the number of decimal digits displayed', function test( t ) {
	var actual;
	var table;
	var group;
	var vals;
	var len;
	var i;

	len = 30;
	vals = new Array( len );
	group = new Array( len );
	for ( i = 0; i < len; i++ ) {
		group[ i ] = discreteUniform( 0, 3 );
		vals[ i ] = ( randu()*50.0 ) + ( 10.0*group[ i ] );
	}
	actual = anova1( group, vals );
	table = actual.print({
		'numdigits': 6
	});
	t.equal( typeof table, 'string', 'returns a pretty-printed table' );
	t.end();
});

tape( 'the function returns an object with a `.print()` method that accepts a `decision` option to control whether the test result should be displayed', function test( t ) {
	var actual;
	var table;
	var group;
	var vals;
	var len;
	var i;

	len = 30;
	vals = new Array( len );
	group = new Array( len );
	for ( i = 0; i < len; i++ ) {
		group[ i ] = discreteUniform( 0, 3 );
		vals[ i ] = ( randu()*50.0 ) + ( 10.0*group[ i ] );
	}
	actual = anova1( group, vals );
	table = actual.print({
		'decision': false
	});
	t.equal( typeof table, 'string', 'returns a pretty-printed table' );
	t.equal( contains( table, 'Test Decision' ), false, 'table does not contain test decision' );
	t.end();
});

tape( 'the function returns an object with a `.print()` method that accepts an `options` object', function test( t ) {
	var table;
	var out;
	var f;
	var x;

	x = [ 3, 4, 5, 6, 2, 5, 10, 12, 8, 10 ];
	f = [ 'treatment A', 'control', 'treatment B', 'treatment A', 'control', 'treatment B', 'treatment A', 'control', 'treatment B', 'treatment A' ];
	out = anova1( x, f );
	table = out.print( {} );

	t.equal( typeof table, 'string', 'returns a pretty-printed table' );
	t.end();
});

tape( 'the function returns an object with a `.print()` method that throws an error if `options` is not a simple object', function test( t ) {
	var values;
	var out;
	var i;
	var x;
	var f;

	x = [ 3, 4, 5, 6, 2, 5, 10, 12, 8, 10 ];
	f = [ 'treatment A', 'control', 'treatment B', 'treatment A', 'control', 'treatment B', 'treatment A', 'control', 'treatment B', 'treatment A' ];
	out = anova1( x, f );

	values = [
		'abc',
		4,
		null,
		true,
		undefined,
		NaN,
		[],
		function noop() {}
	];

	for ( i = 0; i < values.length; i++ ) {
		t.throws( badValue( values[i] ), TypeError, 'throws an error when provided '+values[i] );
	}
	t.end();

	function badValue( value ) {
		return function badValue() {
			out.print( value );
		};
	}
});

tape( 'the function returns an object with a `.print()` method that throws an error if the `numdigits` option is not a positive integer', function test( t ) {
	var values;
	var out;
	var f;
	var i;
	var x;

	x = [ 3, 4, 5, 6, 2, 5, 10, 12, 8, 10 ];
	f = [ 'treatment A', 'control', 'treatment B', 'treatment A', 'control', 'treatment B', 'treatment A', 'control', 'treatment B', 'treatment A' ];
	out = anova1( x, f );

	values = [
		'abc',
		2.4,
		0.0,
		-1.0,
		null,
		true,
		undefined,
		NaN,
		[],
		{},
		function noop() {}
	];

	for ( i = 0; i < values.length; i++ ) {
		t.throws( badValue( values[i] ), TypeError, 'throws an error when provided '+values[i] );
	}
	t.end();

	function badValue( value ) {
		return function badValue() {
			out.print({
				'numdigits': value
			});
		};
	}
});

tape( 'the function returns an object with a `.print()` method that throws an error if the `decision` option is not a boolean primitive', function test( t ) {
	var values;
	var out;
	var f;
	var i;
	var x;

	x = [ 3, 4, 5, 6, 2, 5, 10, 12, 8, 10 ];
	f = [ 'treatment A', 'control', 'treatment B', 'treatment A', 'control', 'treatment B', 'treatment A', 'control', 'treatment B', 'treatment A' ];
	out = anova1( x, f );

	values = [
		'abc',
		2.4,
		0.0,
		-1.0,
		null,
		7.0,
		undefined,
		NaN,
		[],
		{},
		function noop() {}
	];

	for ( i = 0; i < values.length; i++ ) {
		t.throws( badValue( values[i] ), TypeError, 'throws an error when provided '+values[i] );
	}
	t.end();

	function badValue( value ) {
		return function badValue() {
			out.print({
				'decision': value
			});
		};
	}
});
<|endoftext|>


<commit_before>/**
* @license Apache-2.0
*
* Copyright (c) 2018 The Stdlib Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*    http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

'use strict';

// MODULES //

var tape = require( 'tape' );
var noop = require( '@stdlib/utils/noop' );
var Float64Array = require( '@stdlib/array/float64' );
var reduce = require( './../lib' );

// TESTS //

tape( 'main export is a function', function test( t ) {
	t.ok( true, __filename );
	t.strictEqual( typeof reduce, 'function', 'main export is a function' );
	t.end();
});

tape( 'the function throws an error if not provided a collection', function test( t ) {
	var values;
	var i;

	values = [
		'5',
		5,
		NaN,
		true,
		false,
		null,
		void 0,
		{},
		function noop() {},
		/.*/,
		new Date()
	];

	for ( i = 0; i < values.length; i++ ) {
		t.throws( badValue( values[i] ), TypeError, 'throws a type error when provided '+values[i] );
	}
	t.end();

	function badValue( value ) {
		return function badValue() {
			reduce( value, 0, noop );
		};
	}
});

tape( 'the function throws an error if not provided a reduction function', function test( t ) {
	var values;
	var i;

	values = [
		'5',
		5,
		NaN,
		true,
		false,
		null,
		void 0,
		{},
		[],
		/.*/,
		new Date()
	];

	for ( i = 0; i < values.length; i++ ) {
		t.throws( badValue( values[i] ), TypeError, 'throws a type error when provided '+values[i] );
	}
	t.end();

	function badValue( value ) {
		return function badValue() {
			reduce( [ 1, 2, 3 ], 0, value );
		};
	}
});

tape( 'if provided an empty collection, the function returns the initial value and never invokes a reduction function', function test( t ) {
	var arr;
	var out;

	function foo() {
		t.fail( 'should not be invoked' );
	}
	arr = [];

	out = reduce( arr, 0, foo );

	t.strictEqual( out, 0, 'expected result' );
	t.end();
});

tape( 'the function invokes a reduction function for each element in a collection (array)', function test( t ) {
	var arr;
	var out;

	function mult( acc, value ) {
		return acc * value;
	}

	arr = [ 1, 2, 3 ];
	out = reduce( arr, 1, mult );

	t.strictEqual( out, 6, 'returns expected result' );
	t.end();
});

tape( 'the function invokes a reduction function for each element in a collection (array-like object)', function test( t ) {
	var arr;
	var out;

	function mult( acc, value ) {
		return acc * value;
	}

	arr = {
		'length': 3,
		'0': 1,
		'1': 2,
		'2': 3
	};
	out = reduce( arr, 1, mult );

	t.strictEqual( out, 6, 'returns expected result' );
	t.end();
});

tape( 'the function invokes a reduction function for each element in a collection (typed array)', function test( t ) {
	var arr;
	var out;

	function mult( acc, value ) {
		return acc * value;
	}

	arr = new Float64Array( [ 1.0, 2.0, 3.0 ] );
	out = reduce( arr, 1.0, mult );

	t.strictEqual( out, 6.0, 'returns expected result' );
	t.end();
});

tape( 'the function supports providing an execution context', function test( t ) {
	var ctx;
	var arr;
	var out;

	function sum( acc, value ) {
		/* eslint-disable no-invalid-this */
		this.count += 1;
		return acc + value;
	}

	ctx = {
		'count': 0
	};
	arr = [ 1.0, 2.0, 3.0 ];

	out = reduce( arr, 0, sum, ctx );

	t.strictEqual( out/ctx.count, 2.0, 'expected result' );
	t.end();
});

tape( 'the function provides basic support for dynamic arrays', function test( t ) {
	var arr;
	var out;

	function reducer( acc, value, index, collection ) {
		if ( index === collection.length-1 && collection.length < 10 ) {
			collection.push( index+2 );
		}
		return acc + value;
	}

	arr = [ 1, 2, 3 ];
	out = reduce( arr, 0, reducer );

	t.strictEqual( out, 55, 'returns expected result' );
	t.end();
});

tape( 'the function does not skip empty elements', function test( t ) {
	var expected;
	var arr;

	arr = [ 1, , , 4 ]; // eslint-disable-line no-sparse-arrays
	expected = [ 1, void 0, void 0, 4 ];

	function verify( acc, value, index ) {
		t.strictEqual( value, expected[ index ], 'provides expected value' );
		return acc;
	}

	reduce( arr, 0, verify );
	t.end();
});
<commit_msg>Update tests<commit_after>/**
* @license Apache-2.0
*
* Copyright (c) 2018 The Stdlib Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*    http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

'use strict';

// MODULES //

var tape = require( 'tape' );
var noop = require( '@stdlib/utils/noop' );
var naryFunction = require( '@stdlib/utils/nary-function' );
var add = require( '@stdlib/math/base/ops/add' );
var cadd = require( '@stdlib/math/base/ops/cadd' );
var Float64Array = require( '@stdlib/array/float64' );
var Complex64Array = require( '@stdlib/array/complex64' );
var array = require( '@stdlib/ndarray/array' );
var ndarray = require( '@stdlib/ndarray/ctor' );
var Complex64 = require( '@stdlib/complex/float32' );
var realf = require( '@stdlib/complex/realf' );
var imagf = require( '@stdlib/complex/imagf' );
var isnan = require( '@stdlib/math/base/assert/is-nan' );
var reduce = require( './../lib' );

// TESTS //

tape( 'main export is a function', function test( t ) {
	t.ok( true, __filename );
	t.strictEqual( typeof reduce, 'function', 'main export is a function' );
	t.end();
});

tape( 'the function throws an error if not provided a first argument which is either an array-like object or an ndarray', function test( t ) {
	var values;
	var i;

	values = [
		'5',
		5,
		NaN,
		true,
		false,
		null,
		void 0,
		{},
		function noop() {},
		/.*/,
		new Date()
	];

	for ( i = 0; i < values.length; i++ ) {
		t.throws( badValue( values[i] ), TypeError, 'throws a type error when provided '+values[i] );
	}
	t.end();

	function badValue( value ) {
		return function badValue() {
			reduce( value, 0, noop );
		};
	}
});

tape( 'the function throws an error if not provided a third argument which is a function', function test( t ) {
	var values;
	var i;

	values = [
		'5',
		5,
		NaN,
		true,
		false,
		null,
		void 0,
		{},
		[],
		/.*/,
		new Date()
	];

	for ( i = 0; i < values.length; i++ ) {
		t.throws( badValue( values[i] ), TypeError, 'throws a type error when provided '+values[i] );
	}
	t.end();

	function badValue( value ) {
		return function badValue() {
			reduce( [ 1, 2, 3 ], 0, value );
		};
	}
});

tape( 'the function throws an error if not provided a third argument which is a function (context)', function test( t ) {
	var values;
	var i;

	values = [
		'5',
		5,
		NaN,
		true,
		false,
		null,
		void 0,
		{},
		[],
		/.*/,
		new Date()
	];

	for ( i = 0; i < values.length; i++ ) {
		t.throws( badValue( values[i] ), TypeError, 'throws a type error when provided '+values[i] );
	}
	t.end();

	function badValue( value ) {
		return function badValue() {
			reduce( [ 1, 2, 3 ], 0, value, {} );
		};
	}
});

tape( 'the function invokes a reduction function for each element in an array (array-like object)', function test( t ) {
	var expected;
	var actual;
	var arr;
	var f;

	f = naryFunction( add, 2 );

	arr = [ 1, 2, 3 ];
	expected = 7;
	actual = reduce( arr, 1, f );

	t.strictEqual( actual, expected, 'returns expected value' );
	t.end();
});

tape( 'the function invokes a reduction function for each element in an array (typed array)', function test( t ) {
	var expected;
	var actual;
	var arr;
	var f;

	f = naryFunction( add, 2 );

	arr = new Float64Array( [ 1.0, 2.0, 3.0 ] );
	expected = 7.0;
	actual = reduce( arr, 1.0, f );

	t.strictEqual( actual, expected, 'returns expected value' );
	t.end();
});

tape( 'the function invokes a reduction function for each element in an array (complex number array)', function test( t ) {
	var expected;
	var actual;
	var arr;
	var f;

	f = naryFunction( cadd, 2 );

	arr = new Complex64Array( [ 1.0, 2.0, 3.0, 4.0, 5.0, 6.0 ] );
	expected = new Complex64( 9.0, 12.0 );
	actual = reduce( arr, new Complex64( 0.0, 0.0 ), f );

	t.strictEqual( realf( actual ), realf( expected ), 'returns expected value' );
	t.strictEqual( imagf( actual ), imagf( expected ), 'returns expected value' );
	t.end();
});

tape( 'the function invokes a reduction function for each element in an array (ndarray)', function test( t ) {
	var expected;
	var actual;
	var arr;
	var f;

	f = naryFunction( add, 2 );

	arr = array( [ 1.0, 2.0, 3.0, 4.0 ], {
		'dtype': 'generic'
	});
	expected = 11.0;
	actual = reduce( arr, 1.0, f );

	t.strictEqual( actual, expected, 'returns expected value' );
	t.end();
});

tape( 'if provided an empty array, the function returns the initial value and never invokes a reduction function (array-like object)', function test( t ) {
	var out = reduce( [], 0, foo );
	t.strictEqual( out, 0, 'expected result' );
	t.end();

	function foo() {
		t.fail( 'should not be invoked' );
	}
});

tape( 'if provided an empty array, the function returns the initial value and never invokes a reduction function (ndarray)', function test( t ) {
	var arr;
	var out;

	arr = ndarray( 'generic', [], [], [0], 0, 'row-major' );
	out = reduce( arr, 0, foo );
	t.strictEqual( out, 0, 'expected result' );

	t.end();

	function foo() {
		t.fail( 'should not be invoked' );
	}
});

tape( 'the function invokes an applied function with four arguments (array-like object)', function test( t ) {
	var expected;
	var actual;
	var values;
	var arrays;
	var nargs;
	var idx;
	var arr;
	var acc;

	arr = [ 1, 2, 3, 4, 5, 6 ];

	nargs = [];
	acc = [];
	values = [];
	idx = [];
	arrays = [];

	actual = reduce( arr, 0, fcn );

	expected = 21;
	t.strictEqual( actual, expected, 'returns expected value' );

	expected = [ 0, 1, 3, 6, 10, 15 ];
	t.deepEqual( acc, expected, 'returns expected value' );

	expected = [ 1, 2, 3, 4, 5, 6 ];
	t.deepEqual( values, expected, 'returns expected value' );

	expected = [ 0, 1, 2, 3, 4, 5 ];
	t.deepEqual( idx, expected, 'returns expected value' );

	expected = [ arr, arr, arr, arr, arr, arr ];
	t.deepEqual( arrays, expected, 'returns expected value' );

	expected = [ 4, 4, 4, 4, 4, 4 ];
	t.deepEqual( nargs, expected, 'returns expected value' );

	t.end();

	function fcn( accumulator, v, i, arr ) {
		nargs.push( arguments.length );
		acc.push( accumulator );
		values.push( v );
		idx.push( i );
		arrays.push( arr );
		return add( accumulator, v );
	}
});

tape( 'the function invokes an applied function with four arguments (ndarray)', function test( t ) {
	var expected;
	var actual;
	var values;
	var arrays;
	var nargs;
	var idx;
	var arr;
	var acc;

	arr = array( [ 1, 2, 3, 4, 5, 6 ], {
		'dtype': 'generic',
		'shape': [ 2, 3 ]
	});

	nargs = [];
	acc = [];
	values = [];
	idx = [];
	arrays = [];

	actual = reduce( arr, 0, fcn );

	expected = 21;
	t.strictEqual( actual, expected, 'returns expected value' );

	expected = [ 0, 1, 3, 6, 10, 15 ];
	t.deepEqual( acc, expected, 'returns expected value' );

	expected = [ 1, 2, 3, 4, 5, 6 ];
	t.deepEqual( values, expected, 'returns expected value' );

	expected = [ 0, 1, 2, 3, 4, 5 ];
	t.deepEqual( idx, expected, 'returns expected value' );

	expected = [ arr, arr, arr, arr, arr, arr ];
	t.deepEqual( arrays, expected, 'returns expected value' );

	expected = [ 4, 4, 4, 4, 4, 4 ];
	t.deepEqual( nargs, expected, 'returns expected value' );

	t.end();

	function fcn( accumulator, v, i, arr ) {
		nargs.push( arguments.length );
		acc.push( accumulator );
		values.push( v );
		idx.push( i );
		arrays.push( arr );
		return add( accumulator, v );
	}
});

tape( 'the function supports providing a `this` context (array-like object)', function test( t ) {
	var expected;
	var actual;
	var ctx;
	var arr;

	arr = [ 1.0, 2.0, 3.0 ];

	ctx = {
		'count': 0
	};

	expected = 6.0;
	actual = reduce( arr, 0.0, sum, ctx );

	t.strictEqual( actual, expected, 'expected value' );
	t.strictEqual( ctx.count, 3, 'returns expected value' );

	t.end();

	function sum( acc, value ) {
		this.count += 1; // eslint-disable-line no-invalid-this
		return acc + value;
	}
});

tape( 'the function supports providing a `this` context (ndarray)', function test( t ) {
	var expected;
	var actual;
	var ctx;
	var arr;

	arr = array( [ 1.0, 2.0, 3.0 ], {
		'dtype': 'generic'
	});

	ctx = {
		'count': 0
	};

	expected = 6.0;
	actual = reduce( arr, 0.0, sum, ctx );

	t.strictEqual( actual, expected, 'expected value' );
	t.strictEqual( ctx.count, 3, 'returns expected value' );

	t.end();

	function sum( acc, value ) {
		this.count += 1; // eslint-disable-line no-invalid-this
		return acc + value;
	}
});

tape( 'the function does not skip empty elements (array-like object)', function test( t ) {
	var expected;
	var actual;
	var values;
	var arr;

	arr = [ 1, , , 4 ]; // eslint-disable-line no-sparse-arrays

	values = [];

	actual = reduce( arr, 0, sum );

	t.strictEqual( isnan( actual ), true, 'returns expected value' );

	expected = [ 1, void 0, void 0, 4 ];
	t.deepEqual( values, expected, 'returns expected value' );

	t.end();

	function sum( acc, value ) {
		values.push( value );
		return acc + value;
	}
});

tape( 'the function does not skip empty elements (ndarray)', function test( t ) {
	var expected;
	var actual;
	var values;
	var arr;

	arr = array( [ 1, , , 4 ], { // eslint-disable-line no-sparse-arrays
		'dtype': 'generic'
	});

	values = [];

	actual = reduce( arr, 0, sum );

	t.strictEqual( isnan( actual ), true, 'returns expected value' );

	expected = [ 1, void 0, void 0, 4 ];
	t.deepEqual( values, expected, 'returns expected value' );

	t.end();

	function sum( acc, value ) {
		values.push( value );
		return acc + value;
	}
});
<|endoftext|>


<commit_before>import { expect } from "chai";
import React from "react";
import {
    renderIntoDocument,
    scryRenderedDOMComponentsWithTag,
    findRenderedDOMComponentWithClass,
    Simulate
} from "react-addons-test-utils";
import { Provider } from "react-redux";
import CounterPage from "../../app/containers/CounterPage";
import configureStore from "../../app/store/configureStore";

function setup(initialState) {
    const store = configureStore(initialState);
    const app = renderIntoDocument(
        <Provider store={store}>
            <CounterPage />
        </Provider>
    );
    return {
        app: app,
        buttons: scryRenderedDOMComponentsWithTag(app, "button").map(button => {
            return button;
        }),
        p: findRenderedDOMComponentWithClass(app, "counter")
    };
}

describe("containers", () => {
    describe("App", () => {
        it("should display initial count", () => {
            const { p } = setup();
            expect(p.textContent).to.match(/^0$/);
        });

        it("should display updated count after increment button click", () => {
            const { buttons, p } = setup();
            Simulate.click(buttons[1]);
            expect(p.textContent).to.match(/^1$/);
        });

        it("should display updated count after descrement button click", () => {
            const { buttons, p } = setup();
            Simulate.click(buttons[2]);
            expect(p.textContent).to.match(/^-1$/);
        });

        it("shouldn't change if even and if odd button clicked", () => {
            const { buttons, p } = setup();
            Simulate.click(buttons[3]);
            expect(p.textContent).to.match(/^0$/);
        });

        it("should change if odd and if odd button clicked", () => {
            const { buttons, p } = setup({counter: 1});
            Simulate.click(buttons[3]);
            expect(p.textContent).to.match(/^2$/);
        });
    });
});
<commit_msg>removed superfluous statement<commit_after>import { expect } from "chai";
import React from "react";
import {
    renderIntoDocument,
    scryRenderedDOMComponentsWithTag,
    findRenderedDOMComponentWithClass,
    Simulate
} from "react-addons-test-utils";
import { Provider } from "react-redux";
import CounterPage from "../../app/containers/CounterPage";
import configureStore from "../../app/store/configureStore";

function setup(initialState) {
    const store = configureStore(initialState);
    const app = renderIntoDocument(
        <Provider store={store}>
            <CounterPage />
        </Provider>
    );
    return {
        app: app,
        buttons: scryRenderedDOMComponentsWithTag(app, "button"),
        p: findRenderedDOMComponentWithClass(app, "counter")
    };
}

describe("containers", () => {
    describe("App", () => {
        it("should display initial count", () => {
            const { p } = setup();
            expect(p.textContent).to.match(/^0$/);
        });

        it("should display updated count after increment button click", () => {
            const { buttons, p } = setup();
            Simulate.click(buttons[1]);
            expect(p.textContent).to.match(/^1$/);
        });

        it("should display updated count after descrement button click", () => {
            const { buttons, p } = setup();
            Simulate.click(buttons[2]);
            expect(p.textContent).to.match(/^-1$/);
        });

        it("shouldn't change if even and if odd button clicked", () => {
            const { buttons, p } = setup();
            Simulate.click(buttons[3]);
            expect(p.textContent).to.match(/^0$/);
        });

        it("should change if odd and if odd button clicked", () => {
            const { buttons, p } = setup({counter: 1});
            Simulate.click(buttons[3]);
            expect(p.textContent).to.match(/^2$/);
        });
    });
});
<|endoftext|>


<commit_before>'use strict';
var async = require('async');
var restify = require('restify');
var createWebhookHandler = require('github-webhook-handler');
var bunyan = require('bunyan');
var yaml = require('js-yaml');

var GitHubApi = require('github');

var API = require('./api');

/**
 * Create a queue that only processes one task at a time.
 * A task is simply a function that takes a callback when it's done
 */
var statusUpdateQueue = async.queue(function worker(fn, done) {
  fn(done);
}, 1);

var GithubHandler = function(options) {

  this.options = options;

  // Bind functions to ensure `this` works for use in callbacks.
  this.start = this.start.bind(this);
  this.fetchProboYamlConfigFromGithub = this.fetchProboYamlConfigFromGithub.bind(this);
  this.errorHandler = this.errorHandler.bind(this);
  this.pullRequestHandler = this.pullRequestHandler.bind(this);
  this.getGithubApi = this.getGithubApi.bind(this);

  // Instantiate a logger instance.
  var log = bunyan.createLogger({name: 'github-handler',
    level: options.logLevel || 'debug',
    // src: true,
    serializers: {
      err: bunyan.stdSerializers.err,
      req: bunyan.stdSerializers.req,
    },
  });
  var webhookOptions = {
    path: options.githubWebhookPath,
    secret: options.githubWebhookSecret,
  };

  var handler = createWebhookHandler(webhookOptions);
  handler.on('error', this.errorHandler);
  handler.on('pull_request', this.pullRequestHandler);

  var self = this;

  this.server = restify.createServer({log: log, name: 'Probo GHH'});

  // set up request logging
  this.server.use(function(req, res, next) {
    req.log.info({req: req}, 'REQUEST');
    next();
  });
  this.server.on('after', restify.auditLogger({
    log: log,
  }));

  this.server.post(webhookOptions.path, function(req, res, next) {
    handler(req, res, function(error) {
      res.send(400, 'Error processing hook');
      log.error({err: error}, 'Error processing hook');
      next();
    });
  });

  var buildStatusController = function(req, res, next) {
    var payload = req.body;

    if (req.params.context) {
      // usually, context will already be part of update, but read it from URL
      // if it's there for compatability
      payload.update.context = req.params.context;
    }

    log.debug({payload: payload}, 'Update payload');

    self.buildStatusUpdateHandler(payload.update, payload.build, function(err, status) {
      if (err) {
        res.send(500, {error: err});
      }
      else {
        res.send(status);
      }
      return next();
    });
  };

  this.server.post('/builds/:bid/status/:context', restify.jsonBodyParser(), buildStatusController);
  this.server.post('/update', restify.jsonBodyParser(), buildStatusController);

  this.log = log;

  this.api = API.getAPI({
    url: this.options.api.url,
    token: this.options.api.token,
    log: this.log,
    // {url, [host|hostname], [protocol], [port]}
    handler: this.options,
  });

  if (!(this.api instanceof API)) {
    log.info('api.token not found, using Container Manager API directly');
  }
};

GithubHandler.prototype.start = function(done) {
  var self = this;
  this.server.listen({port: self.options.port, host: self.options.hostname || '0.0.0.0'}, function() {
    self.log.info('Now listening on', self.server.url);
    if (done) {
      return done();
    }
  });
};

GithubHandler.prototype.stop = function(done) {
  var self = this;
  var url = this.server.url;
  this.server.close(function() {
    self.log.info('Stopped', url);
    if (done) {
      done();
    }
  });
};

/**
 * Build options for GitHub api HTTP requests.
 *
 * @param {object} project - A project object.
 * @return {object} An instantiated and configured Github API object.
 */
GithubHandler.prototype.getGithubApi = function(project) {
  var github = new GitHubApi({
    version: '3.0.0',
    debug: false,
    protocol: 'https',
    timeout: 5000,
    headers: {
      // GitHub requires a unique user agent.
      'user-agent': 'Probo.CI',
    },
  });

  var auth = {type: 'token', token: this.options.githubAPIToken};
  if (project.service_auth) {
    auth = {type: 'oauth', token: project.service_auth.token};
  }
  github.authenticate(auth);

  return github;
};

/**
 * Error handler for Github webhooks.
 *
 * @param {Error} error - The error that occurred and is being handled.
 */
GithubHandler.prototype.errorHandler = function(error) {
  this.log.error({err: error}, 'An error occurred.');
};

GithubHandler.prototype.pullRequestHandler = function(event, done) {
  // enqueue the event to be processed...
  var self = this;

  this.log.info('Github Pull request ' + event.payload.pull_request.id + ' received');

  // We only want to take action on open and synchronize events (for
  // now, we'll seen want to take action on close events as well).
  if (event.payload.action !== 'opened' && event.payload.action !== 'synchronize') {
    this.log.info(`Github Pull request ${event.payload.pull_request.id} ${event.payload.action}  ignored`);
    return done();
  }

  var request = {
    // Also in event.event.
    type: 'pull_request',
    service: 'github',
    branch: event.payload.pull_request.head.ref,
    branch_html_url: event.payload.repository.html_url + '/tree/' + event.payload.pull_request.head.ref,
    slug: event.payload.repository.full_name,
    owner: event.payload.repository.owner.login,
    repo: event.payload.repository.name,
    repo_id: event.payload.repository.id,
    sha: event.payload.pull_request.head.sha,
    commit_url: event.payload.repository.html_url + '/commit/' + event.payload.pull_request.head.sha,
    pull_request: event.payload.pull_request.number,
    pull_request_id: event.payload.pull_request.id,
    pull_request_name: event.payload.pull_request.title,
    pull_request_description: event.payload.pull_request.body,
    pull_request_html_url: event.payload.pull_request._links.html.href,
    payload: event.payload,
  };

  // Build comes back with an embedded .project key.
  // It's not necessary to do anything here, build status updates will come asyncronously.
  this.processRequest(request, function(error, build) {
    self.log.info({type: request.type, slug: request.slug, err: error}, 'request processed');
    if (done) {
      return done(error, build);
    }
  });
};

/**
 * Called when an build status updates
 *
 * @param {object} update - The update object.
 * @param {string} update.state: "status of build",
 * @param {string} update.description - The text discription of the build state.
 * @param {string} update.context - The context used to differentiate this update from other services and steps.
 * @param {string} update.target_url: The url to link to from the status update.
 * @param {object} build - The full build object.
 * @param {object} build.project - The embedded project object.
 * @param {function} done - The callback to be called after the update is performed.
 */
GithubHandler.prototype.buildStatusUpdateHandler = function(update, build, done) {
  var self = this;
  self.log.info({update: update, build_id: build.id}, 'Got build status update');

  var statusInfo = {
    // Can be one of pending, success, error, or failure.
    state: update.state,
    description: update.description.substring(0, 140),
    context: update.context,
    target_url: update.target_url,
  };

  var task = this.postStatusToGithub.bind(this, build.project, build.commit.ref, statusInfo);
  statusUpdateQueue.push(task, function(error) {
    if (error) {
      self.log.error({err: error, build_id: build.id}, 'An error occurred posting status to GitHub');
      return done(error, statusInfo);
    }

    self.log.info(statusInfo, 'Posted status to Github for', build.project.slug, build.commit.ref);
    done(null, statusInfo);
  });
};

/**
 * @param {object} request - The incoming hook request data.
 * @param {string} request.type - The type of request to process (eg pull_request).
 * @param {string} request.service - The service to be checked (always github in this handler).
 * @param {string} request.slug - The identifier for the repo (repository.full_name from the github api).
 * @param {string} request.event - The entire event payload from the github api call.
 * @param {function} done - The callback to call when finished.
 */
GithubHandler.prototype.processRequest = function(request, done) {
  var self = this;
  self.log.info({type: request.type, id: request.id}, 'Processing request');

  this.api.findProjectByRepo(request, function(error, project) {
    if (error || !project) {
      return self.log.info({error}, `Project for github repo ${request.slug} not found`);
    }

    self.log.info({project: project}, 'Found project for PR');

    self.fetchProboYamlConfigFromGithub(project, request.sha, function(error, config) {
      var build;

      if (error) {
        self.log.error({err: error}, 'Problem fetching Probo Yaml Config file');

        // If we can't find a yaml file we should error.
        build = {
          commit: {ref: request.sha},
          project: project,
        };
        var update = {
          state: 'failure',
          description: error.message,
          context: 'ProboCI/env',
        };
        return self.buildStatusUpdateHandler(update, build, done);
      }

      self.log.info({config: config}, 'Probo Yaml Config file');

      build = {
        commit: {
          ref: request.sha,
          htmlUrl: request.commit_url,
        },
        pullRequest: {
          number: request.pull_request + '',
          name: request.pull_request_name,
          description: request.pull_request_description,
          htmlUrl: request.pull_request_html_url,
        },
        branch: {
          name: request.branch,
          htmlUrl: request.branch_html_url,
        },
        config: config,
        request: request,
      };

      self.api.submitBuild(build, project, function(err, submittedBuild) {
        if (err) {
          // TODO: save the PR if submitting it fails (though logging it here might be ok)
          self.log.error({err: err, request: request, build: build, response: submittedBuild}, 'Problem submitting build');
          return done && done(err);
        }

        self.log.info({build: submittedBuild}, 'Submitted build');

        done(null, submittedBuild);
      });

    });
  });
};

/**
 * Posts status updates to Github.
 *
 * @param {object} project - The project object to post the status for.
 * @param {string} sha - The git commit id that we are posting a status for.
 * @param {string} statusInfo - This should be the status message to post to GH. See https://developer.github.com/v3/repos/statuses/
 * @param {function} done - The callback to call when the status has been updated.
 */
GithubHandler.prototype.postStatusToGithub = function(project, sha, statusInfo, done) {
  var self = this;
  var github = self.getGithubApi(project);

  statusInfo.user = project.owner;
  statusInfo.repo = project.repo;
  statusInfo.sha = sha;

  github.statuses.create(statusInfo, function(error, body) {
    done(error, body);
  });
};

/**
 * Fetches configuration from a .probo.yml file in the github repo.
 *
 * @param {object} project - The project object.
 * @param {string} sha - The git commit id to fetch the .probo.yaml from.
 * @param {function} done - The callback to call upon error/completion.
 */
GithubHandler.prototype.fetchProboYamlConfigFromGithub = function(project, sha, done) {
  var self = this;
  var github = this.getGithubApi(project);

  github.repos.getContent({user: project.owner, repo: project.repo, ref: sha, path: ''}, function(error, files) {
    if (error) return done(error);
    var i = null;
    var regex = /^(.?probo.ya?ml|.?proviso.ya?ml)$/;
    var match = false;
    var file;
    for (i in files) {
      if (files[i]) {
        file = files[i];
        if (regex.test(file.name)) {
          match = true;
          break;
        }
      }
    }
    if (!match) {
      // Should this be an error or just an empty config?
      return done(new Error('No .probo.yml file was found.'));
    }

    github.repos.getContent({user: project.owner, repo: project.repo, ref: sha, path: file.path}, function(error, file) {
      if (error) {
        self.log.error({err: error}, 'Failed to get probo config file contents');

        return done(error);
      }

      var content;
      var settings;
      try {
        content = new Buffer(file.content, 'base64');
        settings = yaml.safeLoad(content.toString('utf8'));
      }
      catch (e) {
        return done(new Error(`Failed to parse ${file.path}:` + e.message));
      }

      done(null, settings);
    });
  });
};

module.exports = GithubHandler;
<commit_msg>Add an HTTP route for returning current information about a pull request.<commit_after>'use strict';
var async = require('async');
var restify = require('restify');
var createWebhookHandler = require('github-webhook-handler');
var bunyan = require('bunyan');
var yaml = require('js-yaml');

var GitHubApi = require('github');

var API = require('./api');

/**
 * Create a queue that only processes one task at a time.
 * A task is simply a function that takes a callback when it's done
 */
var statusUpdateQueue = async.queue(function worker(fn, done) {
  fn(done);
}, 1);

var GithubHandler = function(options) {

  this.options = options;

  // Bind functions to ensure `this` works for use in callbacks.
  this.start = this.start.bind(this);
  this.fetchProboYamlConfigFromGithub = this.fetchProboYamlConfigFromGithub.bind(this);
  this.errorHandler = this.errorHandler.bind(this);
  this.pullRequestHandler = this.pullRequestHandler.bind(this);
  this.getGithubApi = this.getGithubApi.bind(this);

  // Instantiate a logger instance.
  var log = bunyan.createLogger({name: 'github-handler',
    level: options.logLevel || 'debug',
    // src: true,
    serializers: {
      err: bunyan.stdSerializers.err,
      req: bunyan.stdSerializers.req,
    },
  });
  var webhookOptions = {
    path: options.githubWebhookPath,
    secret: options.githubWebhookSecret,
  };

  var handler = createWebhookHandler(webhookOptions);
  handler.on('error', this.errorHandler);
  handler.on('pull_request', this.pullRequestHandler);

  var self = this;

  this.server = restify.createServer({log: log, name: 'Probo GHH'});

  // set up request logging
  this.server.use(function(req, res, next) {
    req.log.info({req: req}, 'REQUEST');
    next();
  });
  this.server.on('after', restify.auditLogger({
    log: log,
  }));

  this.server.post(webhookOptions.path, function(req, res, next) {
    handler(req, res, function(error) {
      res.send(400, 'Error processing hook');
      log.error({err: error}, 'Error processing hook');
      next();
    });
  });
  this.server.use(restify.queryParser());
  this.server.get('/pull-request/:owner/:repo/:pullRequestNumber', function(req, res) {
    console.log(req.query);
    var github = self.getGithubApi({service_auth: req.query});
    var opts = {
      user: req.params.owner,
      repo: req.params.repo,
      number: req.params.pullRequestNumber,
    };
    github.pullRequests.get(opts, function(error, pullRequest) {
      if (error) {
        res.json(500, error);
        return;
      }
      var output = {
        id: pullRequest.id,
        number: pullRequest.number,
        state: pullRequest.state,
        url: pullRequest.url,
        title: pullRequest.title,
        userName: pullRequest.user.login,
        userId: pullRequest.user.id,
      };
      res.json(output);
    });
  });

  var buildStatusController = function(req, res, next) {
    var payload = req.body;

    if (req.params.context) {
      // usually, context will already be part of update, but read it from URL
      // if it's there for compatability
      payload.update.context = req.params.context;
    }

    log.debug({payload: payload}, 'Update payload');

    self.buildStatusUpdateHandler(payload.update, payload.build, function(err, status) {
      if (err) {
        res.send(500, {error: err});
      }
      else {
        res.send(status);
      }
      return next();
    });
  };

  this.server.post('/builds/:bid/status/:context', restify.jsonBodyParser(), buildStatusController);
  this.server.post('/update', restify.jsonBodyParser(), buildStatusController);

  this.log = log;

  this.api = API.getAPI({
    url: this.options.api.url,
    token: this.options.api.token,
    log: this.log,
    // {url, [host|hostname], [protocol], [port]}
    handler: this.options,
  });

  if (!(this.api instanceof API)) {
    log.info('api.token not found, using Container Manager API directly');
  }
};

GithubHandler.prototype.start = function(done) {
  var self = this;
  this.server.listen({port: self.options.port, host: self.options.hostname || '0.0.0.0'}, function() {
    self.log.info('Now listening on', self.server.url);
    if (done) {
      return done();
    }
  });
};

GithubHandler.prototype.stop = function(done) {
  var self = this;
  var url = this.server.url;
  this.server.close(function() {
    self.log.info('Stopped', url);
    if (done) {
      done();
    }
  });
};

/**
 * Build options for GitHub api HTTP requests.
 *
 * @param {object} project - A project object.
 * @return {object} An instantiated and configured Github API object.
 */
GithubHandler.prototype.getGithubApi = function(project) {
  var github = new GitHubApi({
    version: '3.0.0',
    debug: false,
    protocol: 'https',
    timeout: 5000,
    headers: {
      // GitHub requires a unique user agent.
      'user-agent': 'Probo.CI',
    },
  });

  var auth = {type: 'token', token: this.options.githubAPIToken};
  if (project.service_auth) {
    auth = {type: 'oauth', token: project.service_auth.token};
  }
  github.authenticate(auth);

  return github;
};

/**
 * Error handler for Github webhooks.
 *
 * @param {Error} error - The error that occurred and is being handled.
 */
GithubHandler.prototype.errorHandler = function(error) {
  this.log.error({err: error}, 'An error occurred.');
};

GithubHandler.prototype.pullRequestHandler = function(event, done) {
  // enqueue the event to be processed...
  var self = this;

  this.log.info('Github Pull request ' + event.payload.pull_request.id + ' received');

  // We only want to take action on open and synchronize events (for
  // now, we'll seen want to take action on close events as well).
  if (event.payload.action !== 'opened' && event.payload.action !== 'synchronize') {
    this.log.info(`Github Pull request ${event.payload.pull_request.id} ${event.payload.action}  ignored`);
    return done();
  }

  var request = {
    // Also in event.event.
    type: 'pull_request',
    service: 'github',
    branch: event.payload.pull_request.head.ref,
    branch_html_url: event.payload.repository.html_url + '/tree/' + event.payload.pull_request.head.ref,
    slug: event.payload.repository.full_name,
    owner: event.payload.repository.owner.login,
    repo: event.payload.repository.name,
    repo_id: event.payload.repository.id,
    sha: event.payload.pull_request.head.sha,
    commit_url: event.payload.repository.html_url + '/commit/' + event.payload.pull_request.head.sha,
    pull_request: event.payload.pull_request.number,
    pull_request_id: event.payload.pull_request.id,
    pull_request_name: event.payload.pull_request.title,
    pull_request_description: event.payload.pull_request.body,
    pull_request_html_url: event.payload.pull_request._links.html.href,
    payload: event.payload,
  };

  // Build comes back with an embedded .project key.
  // It's not necessary to do anything here, build status updates will come asyncronously.
  this.processRequest(request, function(error, build) {
    self.log.info({type: request.type, slug: request.slug, err: error}, 'request processed');
    if (done) {
      return done(error, build);
    }
  });
};

/**
 * Called when an build status updates
 *
 * @param {object} update - The update object.
 * @param {string} update.state: "status of build",
 * @param {string} update.description - The text discription of the build state.
 * @param {string} update.context - The context used to differentiate this update from other services and steps.
 * @param {string} update.target_url: The url to link to from the status update.
 * @param {object} build - The full build object.
 * @param {object} build.project - The embedded project object.
 * @param {function} done - The callback to be called after the update is performed.
 */
GithubHandler.prototype.buildStatusUpdateHandler = function(update, build, done) {
  var self = this;
  self.log.info({update: update, build_id: build.id}, 'Got build status update');

  var statusInfo = {
    // Can be one of pending, success, error, or failure.
    state: update.state,
    description: update.description.substring(0, 140),
    context: update.context,
    target_url: update.target_url,
  };

  var task = this.postStatusToGithub.bind(this, build.project, build.commit.ref, statusInfo);
  statusUpdateQueue.push(task, function(error) {
    if (error) {
      self.log.error({err: error, build_id: build.id}, 'An error occurred posting status to GitHub');
      return done(error, statusInfo);
    }

    self.log.info(statusInfo, 'Posted status to Github for', build.project.slug, build.commit.ref);
    done(null, statusInfo);
  });
};

/**
 * @param {object} request - The incoming hook request data.
 * @param {string} request.type - The type of request to process (eg pull_request).
 * @param {string} request.service - The service to be checked (always github in this handler).
 * @param {string} request.slug - The identifier for the repo (repository.full_name from the github api).
 * @param {string} request.event - The entire event payload from the github api call.
 * @param {function} done - The callback to call when finished.
 */
GithubHandler.prototype.processRequest = function(request, done) {
  var self = this;
  self.log.info({type: request.type, id: request.id}, 'Processing request');

  this.api.findProjectByRepo(request, function(error, project) {
    if (error || !project) {
      return self.log.info({error}, `Project for github repo ${request.slug} not found`);
    }

    self.log.info({project: project}, 'Found project for PR');

    self.fetchProboYamlConfigFromGithub(project, request.sha, function(error, config) {
      var build;

      if (error) {
        self.log.error({err: error}, 'Problem fetching Probo Yaml Config file');

        // If we can't find a yaml file we should error.
        build = {
          commit: {ref: request.sha},
          project: project,
        };
        var update = {
          state: 'failure',
          description: error.message,
          context: 'ProboCI/env',
        };
        return self.buildStatusUpdateHandler(update, build, done);
      }

      self.log.info({config: config}, 'Probo Yaml Config file');

      build = {
        commit: {
          ref: request.sha,
          htmlUrl: request.commit_url,
        },
        pullRequest: {
          number: request.pull_request + '',
          name: request.pull_request_name,
          description: request.pull_request_description,
          htmlUrl: request.pull_request_html_url,
        },
        branch: {
          name: request.branch,
          htmlUrl: request.branch_html_url,
        },
        config: config,
        request: request,
      };

      self.api.submitBuild(build, project, function(err, submittedBuild) {
        if (err) {
          // TODO: save the PR if submitting it fails (though logging it here might be ok)
          self.log.error({err: err, request: request, build: build, response: submittedBuild}, 'Problem submitting build');
          return done && done(err);
        }

        self.log.info({build: submittedBuild}, 'Submitted build');

        done(null, submittedBuild);
      });

    });
  });
};

/**
 * Posts status updates to Github.
 *
 * @param {object} project - The project object to post the status for.
 * @param {string} sha - The git commit id that we are posting a status for.
 * @param {string} statusInfo - This should be the status message to post to GH. See https://developer.github.com/v3/repos/statuses/
 * @param {function} done - The callback to call when the status has been updated.
 */
GithubHandler.prototype.postStatusToGithub = function(project, sha, statusInfo, done) {
  var self = this;
  var github = self.getGithubApi(project);

  statusInfo.user = project.owner;
  statusInfo.repo = project.repo;
  statusInfo.sha = sha;

  github.statuses.create(statusInfo, function(error, body) {
    done(error, body);
  });
};

/**
 * Fetches configuration from a .probo.yml file in the github repo.
 *
 * @param {object} project - The project object.
 * @param {string} sha - The git commit id to fetch the .probo.yaml from.
 * @param {function} done - The callback to call upon error/completion.
 */
GithubHandler.prototype.fetchProboYamlConfigFromGithub = function(project, sha, done) {
  var self = this;
  var github = this.getGithubApi(project);

  github.repos.getContent({user: project.owner, repo: project.repo, ref: sha, path: ''}, function(error, files) {
    if (error) return done(error);
    var i = null;
    var regex = /^(.?probo.ya?ml|.?proviso.ya?ml)$/;
    var match = false;
    var file;
    for (i in files) {
      if (files[i]) {
        file = files[i];
        if (regex.test(file.name)) {
          match = true;
          break;
        }
      }
    }
    if (!match) {
      // Should this be an error or just an empty config?
      return done(new Error('No .probo.yml file was found.'));
    }

    github.repos.getContent({user: project.owner, repo: project.repo, ref: sha, path: file.path}, function(error, file) {
      if (error) {
        self.log.error({err: error}, 'Failed to get probo config file contents');

        return done(error);
      }

      var content;
      var settings;
      try {
        content = new Buffer(file.content, 'base64');
        settings = yaml.safeLoad(content.toString('utf8'));
      }
      catch (e) {
        return done(new Error(`Failed to parse ${file.path}:` + e.message));
      }

      done(null, settings);
    });
  });
};

module.exports = GithubHandler;
<|endoftext|>


<commit_before>var path = require('path')
  ,http = require('http')
  ,async = require('async')
  ,restify = require('restify')
  ,request = require('request')
  ,createWebhookHandler = require('github-webhook-handler')
  ,bunyan = require('bunyan')
  ,yaml = require('js-yaml')
;

var GitHubApi = require("github");

var API = require('./api');

var GithubHandler = function(options) {

  this.options = options;

  // Bind functions to ensure `this` works for use in callbacks.
  this.start = this.start.bind(this);
  this.fetchProvisoYamlConfigFromGithub = this.fetchProvisoYamlConfigFromGithub.bind(this);
  this.errorHandler = this.errorHandler.bind(this);
  this.pullRequestHandler = this.pullRequestHandler.bind(this);
  this.getGithubApi = this.getGithubApi.bind(this);

  // Instantiate a logger instance.
  var log  = bunyan.createLogger({name: 'github-handler',
                                  // src: true,
                                  serializers: {
                                    err: bunyan.stdSerializers.err
                                  }});
  var options = {
    path: options.githubWebhookPath,
    secret: options.githubWebhookSecret,
  };

  var handler = createWebhookHandler(options);
  handler.on('error', this.errorHandler);
  handler.on('pull_request', this.pullRequestHandler);

  var self = this;

  // TODO: use restify for this
  this.server = http.createServer(function(req, res) {
    if(req.url == options.path){
      handler(req, res, function(error) {
        res.writeHead(404);
        log.error({ err: error}, '404 endpoint not found');
        res.end('Not found');
      });
    }
    // our own custom routing for now
    else if(req.url == "/update"){
      var bl = require('bl')

      req.pipe(bl(function(err, data){
        var payload;
        try {
          payload = JSON.parse(data.toString())
        } catch (e) {
          res.writeHead(400, { 'content-type': 'application/json' })
          res.end(JSON.stringify({error: "Bad payload: " + e.message, body: data.toString()}));
          return;
        }

        self.buildStatusUpdateHandler(payload.update, payload.build, function(err){
          res.writeHead(err ? 500 : 200, { 'content-type': 'application/json' }, err)

          if(err){
            res.end(JSON.stringify({error: JSON.stringify(err)}));
          } else {
            res.end(JSON.stringify({success: true}));
          }
        });
      }));
    }
  });
  this.log = log;

  this.api = new API({
    url: this.options.api.url,
    token: this.options.api.token,
    log: this.log
  })
};

/**
 * Starts the server listening on the configured port.
 */
GithubHandler.prototype.start = function(cb) {
  var self = this;
  this.server.listen({port: self.options.port, host: '0.0.0.0'}, function() {
    self.log.info(self.server.address(), 'Now listening on');
    cb && cb();
  });
};

GithubHandler.prototype.stop = function(cb) {
  var self = this;
  this.server.close(function() {
    self.log.info(self.server.address(), 'Stopped');
    cb && cb();
  });
};

/**
 * Build options for GitHub api HTTP requests.
 */
GithubHandler.prototype.getGithubApi = function(project) {
  var github = new GitHubApi({
    // required
    version: "3.0.0",
    // optional
    // debug: true,
    protocol: "https",
    // host: "github.my-GHE-enabled-company.com",
    // pathPrefix: "/api/v3", // for some GHEs
    timeout: 5000,
    headers: {
      "user-agent": "Proviso" // GitHub is happy with a unique user agent
    }
  });

  var auth = project.service_auth || {token: this.options.githubAPIToken, type: 'token'};
  github.authenticate(auth);

  return github;
}

/**
 * Error handler for Github webhooks.
 */
GithubHandler.prototype.errorHandler = function(error) {
  this.log.error({err: error}, 'An error occurred.');
};

/**
 * Handler for pull request events.
 */
GithubHandler.prototype.pullRequestHandler = function(event) {
  // enqueue the event to be processed...
  var self = this;

  this.log.info('Github Pull request ' + event.payload.pull_request.id + ' received');

  // 1. [DONE] GHH sends commit to API
  // 2. [TODO] API starts container manager
  // 3. [DONE] Container manager sends status updates back to the API
  // 4. [DONE] API udpates GHH (which updates github)
  //    Sends GH token for GHH to use with request
  //    GHH has a default token that it uses if it's not provided

  var request = {
    type: 'pull_request', // also in event.event
    service: 'github',
    host: undefined,
    slug: event.payload.repository.full_name,
    sha: event.payload.pull_request.head.sha,
    payload: event.payload,
    id: event.payload.pull_request.id
  }

  /**
   * build comes back with an embedded .project
   * not necessary to do anything here, build status updates will come asyncronously
   */
  this.processRequest(request, function(error, build){
    self.log.info({type: request.type, slug: request.slug, err: error}, "request processed");
  });
}

/**
 * Called when an build status updates
 *
 * update = {
 *  status: "status of build",
 *  description: "",
 *  context: "the context",
 *  target_url: ""
 * }
 *
 * build has an embedded .project too
 */
GithubHandler.prototype.buildStatusUpdateHandler = function(update, build, cb){
  var self = this;
  self.log.info({update: update, build_id: build.id}, "Got build status update");

  var statusInfo = {
    state: update.status,  // can be one of pending, success, error, or failure.
    description: update.description,
    context: update.context,
    target_url: update.target_url
  }

  self.postStatusToGithub(build.project, build.sha, statusInfo, function(error){
    if (error) {
      self.log.error({ err: error, build_id: build.id }, 'An error occurred posting status to GitHub');
      return cb(error);
    }

    self.log.info(statusInfo, 'Posted status to Github for Pull Request ' + request.id);
    cb();
  });
}

/**
 * request: {type, service, slug, event}
 */
GithubHandler.prototype.processRequest = function(request, cb){
  var self = this;
  self.log.info({type: request.type, id: request.id}, 'Processing request');

  this.api.findProjectByRepo(request.service, request.slug, request.host, function(error, project){
    if(error || !project){
      return self.log.info(
        {err: error},
        "Project for github repo " + request.slug + " not found"
      );
    }

    self.log.info({project: project}, "Found project for PR");

    self.fetchProvisoYamlConfigFromGithub(project, request.sha, function(error, config) {
      self.log.info({err: error, config: config}, "Proviso Yaml Config file");

      var build = {
        status: 'pending',
        sha: request.sha,
        config: config,
        request: request
      };

      self.api.submitBuild(project.id, build, function(err, build){
        if(err){
          // TODO: though the PR if submitting it fails (though logging it here might be ok)
          self.log.error({err: err, request: request, build: build}, "Problem submitting build");
          return cb && cb(err);
        }

        self.log.info({build: build}, "Submitted build");

        var statusInfo = {
          state: "pending",
          description: "Building environment " + new Date(),
          context: "ci/env"
        }

        self.postStatusToGithub(build.project, build.sha, statusInfo, function(error){
          if (error) {
            self.log.error({ err: error, build_id: build.id }, 'An error occurred posting status to GitHub');
            return cb();
          }

          self.log.info(statusInfo, 'Posted status to Github for Pull Request ' + request.id);
          cb();
        });
      });
    });
  });
};

/**
 * Posts status updates to Github.
 *
 * statusInfo should be the status message to post to GH - see https://developer.github.com/v3/repos/statuses/
 */
GithubHandler.prototype.postStatusToGithub = function(project, sha, statusInfo, done) {
  var self = this;
  var github = self.getGithubApi(project);

  statusInfo.user = project.owner;
  statusInfo.repo = project.repo;
  statusInfo.sha = sha;

  github.statuses.create(statusInfo, function(error, body){
    done(error, body);
  })
}

/**
 * Fetches configuration from a .proviso.yml file in the github repo.
 */
GithubHandler.prototype.fetchProvisoYamlConfigFromGithub = function(project, sha, done) {
  var self = this;
  var path = '/contents?ref=' + sha;
  var github = this.getGithubApi(project);
  github.repos.getContent({user: project.owner, repo: project.repo, ref: sha, path: ''}, function(error, files){
    if (error) return done(error)

    var i = null;
    var regex = /.?proviso.ya?ml/;
    var match = false;
    var file;
    for (i in files) {
      file = files[i];
      if (regex.test(file.name)) {
        match = true;
        break;
      }
    }
    if (!match) {
      // Should this be an error or just an empty config?
      done(new Error('No .proviso.yml file was found.'));
    }

    github.repos.getContent({user: project.owner, repo: project.repo, ref: sha, path: file.path}, function(error, file){
      if (error) return done(error);
      var content = new Buffer(file.content, 'base64');
      var settings = yaml.safeLoad(content.toString('utf8'));
      done(null, settings);
    });
  });
};

module.exports = GithubHandler;
<commit_msg>if no API is configured, just return project details from request info<commit_after>var path = require('path')
  ,http = require('http')
  ,async = require('async')
  ,restify = require('restify')
  ,request = require('request')
  ,createWebhookHandler = require('github-webhook-handler')
  ,bunyan = require('bunyan')
  ,yaml = require('js-yaml')
;

var GitHubApi = require("github");

var API = require('./api');

var GithubHandler = function(options) {

  this.options = options;

  // Bind functions to ensure `this` works for use in callbacks.
  this.start = this.start.bind(this);
  this.fetchProvisoYamlConfigFromGithub = this.fetchProvisoYamlConfigFromGithub.bind(this);
  this.errorHandler = this.errorHandler.bind(this);
  this.pullRequestHandler = this.pullRequestHandler.bind(this);
  this.getGithubApi = this.getGithubApi.bind(this);

  // Instantiate a logger instance.
  var log  = bunyan.createLogger({name: 'github-handler',
                                  // src: true,
                                  serializers: {
                                    err: bunyan.stdSerializers.err
                                  }});
  var options = {
    path: options.githubWebhookPath,
    secret: options.githubWebhookSecret,
  };

  var handler = createWebhookHandler(options);
  handler.on('error', this.errorHandler);
  handler.on('pull_request', this.pullRequestHandler);

  var self = this;

  // TODO: use restify for this
  this.server = http.createServer(function(req, res) {
    if(req.url == options.path){
      handler(req, res, function(error) {
        res.writeHead(404);
        log.error({ err: error}, '404 endpoint not found');
        res.end('Not found');
      });
    }
    // our own custom routing for now
    else if(req.url == "/update"){
      var bl = require('bl')

      req.pipe(bl(function(err, data){
        var payload;
        try {
          payload = JSON.parse(data.toString())
        } catch (e) {
          res.writeHead(400, { 'content-type': 'application/json' })
          res.end(JSON.stringify({error: "Bad payload: " + e.message, body: data.toString()}));
          return;
        }

        self.buildStatusUpdateHandler(payload.update, payload.build, function(err){
          res.writeHead(err ? 500 : 200, { 'content-type': 'application/json' }, err)

          if(err){
            res.end(JSON.stringify({error: JSON.stringify(err)}));
          } else {
            res.end(JSON.stringify({success: true}));
          }
        });
      }));
    } else {
      res.writeHead(404);
      res.end('Not found: ' + req.url);
    }
  });
  this.log = log;

  this.api = new API({
    url: this.options.api.url,
    token: this.options.api.token,
    log: this.log
  })
};

/**
 * Starts the server listening on the configured port.
 */
GithubHandler.prototype.start = function(cb) {
  var self = this;
  this.server.listen({port: self.options.port, host: '0.0.0.0'}, function() {
    self.log.info(self.server.address(), 'Now listening on');
    cb && cb();
  });
};

GithubHandler.prototype.stop = function(cb) {
  var self = this;
  this.server.close(function() {
    self.log.info(self.server.address(), 'Stopped');
    cb && cb();
  });
};

/**
 * Build options for GitHub api HTTP requests.
 */
GithubHandler.prototype.getGithubApi = function(project) {
  var github = new GitHubApi({
    // required
    version: "3.0.0",
    // optional
    // debug: true,
    protocol: "https",
    // host: "github.my-GHE-enabled-company.com",
    // pathPrefix: "/api/v3", // for some GHEs
    timeout: 5000,
    headers: {
      "user-agent": "Proviso" // GitHub is happy with a unique user agent
    }
  });

  var auth = project.service_auth || {token: this.options.githubAPIToken, type: 'token'};
  github.authenticate(auth);

  return github;
}

/**
 * Error handler for Github webhooks.
 */
GithubHandler.prototype.errorHandler = function(error) {
  this.log.error({err: error}, 'An error occurred.');
};

/**
 * Handler for pull request events.
 */
GithubHandler.prototype.pullRequestHandler = function(event) {
  // enqueue the event to be processed...
  var self = this;

  this.log.info('Github Pull request ' + event.payload.pull_request.id + ' received');

  var request = {
    type: 'pull_request', // also in event.event
    service: 'github',
    host: undefined,
    slug: event.payload.repository.full_name,
    owner: event.payload.repository.owner.login,
    repo: event.payload.repository.name,
    sha: event.payload.pull_request.head.sha,
    payload: event.payload,
    id: event.payload.pull_request.id
  }

  /**
   * build comes back with an embedded .project
   * not necessary to do anything here, build status updates will come asyncronously
   */
  this.processRequest(request, function(error, build){
    self.log.info({type: request.type, slug: request.slug, err: error}, "request processed");
  });
}

/**
 * Called when an build status updates
 *
 * update = {
 *  status: "status of build",
 *  description: "",
 *  context: "the context",
 *  target_url: ""
 * }
 *
 * build has an embedded .project too
 */
GithubHandler.prototype.buildStatusUpdateHandler = function(update, build, cb){
  var self = this;
  self.log.info({update: update, build_id: build.id}, "Got build status update");

  var statusInfo = {
    state: update.status,  // can be one of pending, success, error, or failure.
    description: update.description,
    context: update.context,
    target_url: update.target_url
  }

  self.postStatusToGithub(build.project, build.sha, statusInfo, function(error){
    if (error) {
      self.log.error({ err: error, build_id: build.id }, 'An error occurred posting status to GitHub');
      return cb(error);
    }

    self.log.info(statusInfo, 'Posted status to Github for Pull Request ' + request.id);
    cb();
  });
}

GithubHandler.prototype.getProjectInfo = function(request, cb){
  if(this.options.api.url){
    return this.api.findProjectByRepo(request.service, request.slug, request.host, cb);
  } else {
    setImmediate(function(){
      cb(null, {
        owner: request.owner,
        repo: request.repo,
        service_auth: {token: this.options.githubAPIToken, type: 'token'}
      });
    });
  }
}

/**
 * request: {type, service, slug, event}
 */
GithubHandler.prototype.processRequest = function(request, cb){
  var self = this;
  self.log.info({type: request.type, id: request.id}, 'Processing request');

  this.getProjectInfo(request, function(error, project){
    if(error || !project){
      return self.log.info(
        {err: error},
        "Project for github repo " + request.slug + " not found"
      );
    }

    self.log.info({project: project}, "Found project for PR");

    self.fetchProvisoYamlConfigFromGithub(project, request.sha, function(error, config) {
      self.log.info({err: error, config: config}, "Proviso Yaml Config file");

      var build = {
        status: 'pending',
        sha: request.sha,
        config: config,
        request: request
      };

      self.api.submitBuild(project.id, build, function(err, build){
        if(err){
          // TODO: though the PR if submitting it fails (though logging it here might be ok)
          self.log.error({err: err, request: request, build: build}, "Problem submitting build");
          return cb && cb(err);
        }

        self.log.info({build: build}, "Submitted build");

        var statusInfo = {
          state: "pending",
          description: "Building environment " + new Date(),
          context: "ci/env"
        }

        self.postStatusToGithub(build.project, build.sha, statusInfo, function(error){
          if (error) {
            self.log.error({ err: error, build_id: build.id }, 'An error occurred posting status to GitHub');
            return cb();
          }

          self.log.info(statusInfo, 'Posted status to Github for Pull Request ' + request.id);
          cb();
        });
      });
    });
  });
};

/**
 * Posts status updates to Github.
 *
 * statusInfo should be the status message to post to GH - see https://developer.github.com/v3/repos/statuses/
 */
GithubHandler.prototype.postStatusToGithub = function(project, sha, statusInfo, done) {
  var self = this;
  var github = self.getGithubApi(project);

  statusInfo.user = project.owner;
  statusInfo.repo = project.repo;
  statusInfo.sha = sha;

  github.statuses.create(statusInfo, function(error, body){
    done(error, body);
  })
}

/**
 * Fetches configuration from a .proviso.yml file in the github repo.
 */
GithubHandler.prototype.fetchProvisoYamlConfigFromGithub = function(project, sha, done) {
  var self = this;
  var path = '/contents?ref=' + sha;
  var github = this.getGithubApi(project);
  github.repos.getContent({user: project.owner, repo: project.repo, ref: sha, path: ''}, function(error, files){
    if (error) return done(error)

    var i = null;
    var regex = /.?proviso.ya?ml/;
    var match = false;
    var file;
    for (i in files) {
      file = files[i];
      if (regex.test(file.name)) {
        match = true;
        break;
      }
    }
    if (!match) {
      // Should this be an error or just an empty config?
      done(new Error('No .proviso.yml file was found.'));
    }

    github.repos.getContent({user: project.owner, repo: project.repo, ref: sha, path: file.path}, function(error, file){
      if (error) return done(error);
      var content = new Buffer(file.content, 'base64');
      var settings = yaml.safeLoad(content.toString('utf8'));
      done(null, settings);
    });
  });
};

module.exports = GithubHandler;
<|endoftext|>


<commit_before>var test = require('tape')
const {handleMessages} = require('././utils')
const canvasApi = require('../../canvasApi')
const randomstring = require('randomstring')

function processMessage (message, course) {
  // First create a fresch course in canvas
  let canvasCourse
  return canvasApi.createCourse({course}, 14) // Courses that starts with an 'A' is handled by account 14
  .catch(err => console.error(err))
  .then(res => { canvasCourse = res })
  .then(() => handleMessages(message))
  .then(([{resp}]) => canvasApi.pollUntilSisComplete(resp.id))
  .then(() => canvasApi.getEnrollments(canvasCourse.id))
  .then(([enrolledUser]) => enrolledUser)
}

test('should enroll an assistant in an existing course in canvas', t => {
  t.plan(1)

  const courseCode = 'A' + randomstring.generate(5)
  const userKthId = 'u1znmoik'
  const message = {
    ugClass: 'group',
    ug1Name: `edu.courses.SF.${courseCode}.20171.1.assistants`,
    member: [userKthId]}

  const course = {
    name: 'Emil testar',
    'course_code': courseCode,
    'sis_course_id': `${courseCode}VT171`
  }

  processMessage(message, course)
  .then((enrolledUser) => {
    t.equal(enrolledUser.sis_user_id, userKthId)
  })
})

test('should enroll a re-registered student in an existing course in canvas', t => {
  t.plan(2)
  const userKthId = 'u1znmoik'
  const courseCode0 = 'A' + randomstring.generate(1)
  const courseCode1 = randomstring.generate(4)

  const message = {
    ugClass: 'group',
    ug1Name: `ladok2.kurser.${courseCode0}.${courseCode1}.omregistrerade_20171`,
    member: [userKthId]}

  const course = {
    name: 'Emil testar',
    'course_code': courseCode0 + courseCode1,
    'sis_course_id': `${courseCode0 + courseCode1}VT173`
  }

  processMessage(message, course)
  .then(enrolledUser => {
    t.ok(enrolledUser)
    t.equal(enrolledUser.sis_user_id, userKthId)
  })
})

test('should enroll a student in an existing course in canvas', t => {
  t.plan(2)

  const courseCode0 = 'A' + randomstring.generate(1)
  const courseCode1 = randomstring.generate(4)
  const userKthId = 'u1znmoik'

  const message = {
    kthid: 'u2yp4zyn',
    ugClass: 'group',
    ug1Name: `ladok2.kurser.${courseCode0}.${courseCode1}.registrerade_20171.1`,
    member: [userKthId]}

  const course = {
    name: 'Emil testar',
    'course_code': courseCode0 + courseCode1,
    'sis_course_id': `${courseCode0 + courseCode1}VT171`
  }

  processMessage(message, course)
  .then((enrolledUser) => {
    t.ok(enrolledUser)
    t.equal(enrolledUser.sis_user_id, userKthId)
  })
})
<commit_msg>skip failing test for now<commit_after>var test = require('tape')
const {handleMessages} = require('././utils')
const canvasApi = require('../../canvasApi')
const randomstring = require('randomstring')

function processMessage (message, course) {
  // First create a fresch course in canvas
  let canvasCourse
  return canvasApi.createCourse({course}, 14) // Courses that starts with an 'A' is handled by account 14
  .catch(err => console.error(err))
  .then(res => { canvasCourse = res })
  .then(() => handleMessages(message))
  .then(([{resp}]) => canvasApi.pollUntilSisComplete(resp.id))
  .then(() => canvasApi.getEnrollments(canvasCourse.id))
  .then(([enrolledUser]) => enrolledUser)
}

test('should enroll an assistant in an existing course in canvas', t => {
  t.plan(1)

  const courseCode = 'A' + randomstring.generate(5)
  const userKthId = 'u1znmoik'
  const message = {
    ugClass: 'group',
    ug1Name: `edu.courses.SF.${courseCode}.20171.1.assistants`,
    member: [userKthId]}

  const course = {
    name: 'Emil testar',
    'course_code': courseCode,
    'sis_course_id': `${courseCode}VT171`
  }

  processMessage(message, course)
  .then((enrolledUser) => {
    t.equal(enrolledUser.sis_user_id, userKthId)
  })
})

test('should enroll a re-registered student in an existing course in canvas', t => {
  t.plan(2)
  const userKthId = 'u1znmoik'
  const courseCode0 = 'A' + randomstring.generate(1)
  const courseCode1 = randomstring.generate(4)

  const message = {
    ugClass: 'group',
    ug1Name: `ladok2.kurser.${courseCode0}.${courseCode1}.omregistrerade_20171`,
    member: [userKthId]}

  const course = {
    name: 'Emil testar',
    'course_code': courseCode0 + courseCode1,
    'sis_course_id': `${courseCode0 + courseCode1}VT173`
  }

  processMessage(message, course)
  .then(enrolledUser => {
    t.ok(enrolledUser)
    t.equal(enrolledUser.sis_user_id, userKthId)
  })
})

test('should enroll a student in an existing course in canvas', t => {
  t.plan(2)

  const courseCode0 = 'A' + randomstring.generate(1)
  const courseCode1 = randomstring.generate(4)
  const userKthId = 'u1znmoik'

  const message = {
    kthid: 'u2yp4zyn',
    ugClass: 'group',
    ug1Name: `ladok2.kurser.${courseCode0}.${courseCode1}.registrerade_20171.1`,
    member: [userKthId]}

  const course = {
    name: 'Emil testar',
    'course_code': courseCode0 + courseCode1,
    'sis_course_id': `${courseCode0 + courseCode1}VT171`
  }

  processMessage(message, course)
  .then((enrolledUser) => {
    t.ok(enrolledUser)
    t.equal(enrolledUser.sis_user_id, userKthId)
  })
})

test.skip('should enroll an observer 👀 in an existing course in canvas', t => {
  t.plan(2)

  const courseCode0 = 'A' + randomstring.generate(1)
  const courseCode1 = randomstring.generate(4)
  const userKthId = 'u1znmoik'

  const message = {
    kthid: 'u2vvutyd',
    ugClass: 'group',
    deleted: false,
    ug1Name: `ladok2.kurser.${courseCode0}.${courseCode1}_20171.1`,
    member: [ userKthId ]
  }

  const course = {
    name: 'Emil testar',
    'course_code': courseCode0 + courseCode1,
    'sis_course_id': `${courseCode0 + courseCode1}VT171`
  }

  processMessage(message, course)
  .then((enrolledUser) => {
    console.log('TODO: SHOULD CHECK THE ROLE OF THE USER!')
    console.log(JSON.stringify(enrolledUser, null, 4))
    t.ok(enrolledUser)
    t.equal(enrolledUser.sis_user_id, userKthId)
  })
})
<|endoftext|>