1
0
Fork 0
liza/test/dapi/http/NodeHttpImplTest.js

461 lines
13 KiB
JavaScript

/**
* Test HTTP using Node.js-compatible API
*
* Copyright (C) 2017 R-T Specialty, LLC.
*
* This file is part of the Liza Data Collection Framework.
*
* liza is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
'use strict';
const { expect } = require( 'chai' );
const { Class } = require( 'easejs' );
const {
HttpImpl,
NodeHttpImpl: Sut,
HttpError,
} = require( '../../../' ).dapi.http;
describe( "NodeHttpImpl", () =>
{
it( 'is an HttpImpl', function()
{
var sut = Sut( function() {} );
expect( Class.isA( HttpImpl, sut ) ).to.be.ok;
} );
[
{
label: "uses http for plain HTTP requests",
protocol: 'http:',
method: 'GET',
},
{
label: "uses http for plain HTTP requests",
protocol: 'https:',
method: 'GET',
}
].forEach( ( { label, protocol, method } ) =>
{
it( label, done =>
{
const url_result = {
protocol: protocol,
hostname: 'host',
port: 8888,
path: 'foo',
};
const url = _createMockUrl( given_url => url_result );
const data = {};
const callback_expected = {};
const callback = () => callback_expected;
const check = proto => ( opts, given_callback ) =>
{
expect( opts.protocol ).to.equal( proto );
expect( opts.hostname ).to.equal( url_result.hostname );
expect( opts.port ).to.equal( url_result.port );
expect( opts.path ).to.equal( url_result.path );
expect( opts.method ).to.equal( method );
given_callback( _createMockResp() );
done();
};
const http = _createMockHttp( check( 'http:' ) );
const https = _createMockHttp( check( 'https:' ) );
Sut( { http: http, https: https }, url )
.requestData( '', method, data, callback );
} );
} );
describe( "given an origin", () =>
{
it( "prepends to URL if URL begins with a slash", done =>
{
const origin = 'https://foo.com';
const path = '/quux/quuux';
const url = _createMockUrl( given_url =>
{
expect( given_url ).to.equal( origin + path );
done();
} );
const http = _createMockHttp( ( _, callback ) =>
{
callback( res );
res.trigger( 'end' );
} );
Sut( { http: http }, url, origin )
.requestData( path, 'GET', {}, () => {} );
} );
it( "does not prepend to URL that does not begin with a slash", done =>
{
const origin = 'https://bar.com';
const path = 'http://foo.com/quux/quuux';
const url = _createMockUrl( given_url =>
{
expect( given_url ).to.equal( path );
done();
} );
const http = _createMockHttp( ( _, callback ) =>
{
callback( res );
res.trigger( 'end' );
} );
Sut( { http: http }, url, origin )
.requestData( path, 'GET', {}, () => {} );
} );
} );
it( "returns response when no error", done =>
{
const res = _createMockResp();
const chunks = [ 'a', 'b', 'c', 'd' ];
const http = _createMockHttp( ( _, callback ) =>
{
callback( res );
chunks.forEach( chunk => res.trigger( 'data', chunk ) );
res.trigger( 'end' );
} );
Sut( { http: http }, _createMockUrl() )
.requestData( "", 'GET', '', ( e, data ) =>
{
expect( e ).to.equal( null );
expect( data ).to.equal( chunks.join( '' ) );
done();
} );
} );
it( "adds data to query string on GET", done =>
{
const given_path = '/path';
const expected_query = 'write data';
const res = _createMockResp();
const url = _createMockUrl( given_url => ( {
protocol: 'http:',
path: given_path,
} ) );
const http = _createMockHttp( ( options, callback ) =>
{
expect( options.path )
.to.equal( given_path + '?' + expected_query );
callback( res );
res.trigger( 'end' );
} );
Sut( { http: http }, url )
.requestData( "", 'GET', expected_query, done );
} );
it( "writes form data on POST", done =>
{
const expected_data = 'expected';
const expected_write = 'write data';
const res = _createMockResp();
const http = _createMockHttp( ( options, callback ) =>
{
expect( http.req.written ).to.equal( expected_write );
expect( options.headers[ 'Content-Type' ] )
.to.equal( 'application/x-www-form-urlencoded' );
callback( res );
// make sure we're still handling the response as well
res.trigger( 'data', expected_data );
res.trigger( 'end' );
} );
Sut( { http: http }, _createMockUrl() )
.requestData( "", 'POST', expected_write, ( e, data ) =>
{
expect( e ).to.equal( null );
expect( data ).to.equal( expected_data );
done();
} );
} );
it( "returns error and response given non-200 status code", done =>
{
const res = _createMockResp();
const http = _createMockHttp( ( _, callback ) =>
{
callback( res )
res.statusCode = 418;
res.statusMessage = "I'm a teapot";
res.trigger( 'end' );
} );
Sut( { http: http }, _createMockUrl() )
.requestData( "", 'GET', '', ( e, data ) =>
{
expect( e ).to.be.instanceOf( HttpError );
expect( e.message ).to.equal( res.statusMessage );
expect( e.statuscode ).to.equal( res.statusCode );
done();
} );
} );
describe( "given a request error", () =>
{
it( "returns error with no response on request error", done =>
{
const error = Error( 'test error' );
const http = _createMockHttp( () => {} );
Sut( { http: http }, _createMockUrl() )
.requestData( "", 'GET', '', ( e, data ) =>
{
expect( data ).to.equal( null );
expect( e ).to.equal( error );
done();
} );
// request will be hanging at this point since we didn't call
// the callback, so we can fail the request
http.req.trigger( 'error', error );
} );
// this should never happen in practice, but we want to defend
// against it to make sure the callback is not invoked twice
it( "will not complete request on end", () =>
{
let res = _createMockResp();
const http = _createMockHttp( ( _, callback ) =>
{
// allow hooking `end'
callback( res );
} );
Sut( { http: http }, _createMockUrl() )
.requestData( "", 'GET', '', ( e, data ) =>
{
// will fail on successful callback
expect( data ).to.equal( null );
} );
http.req.trigger( 'error', Error() );
// do not invoke a second time (should do nothing)
res.trigger( 'end' );
} );
} );
describe( "protected API", () =>
{
it( "allows overriding request end behavior", done =>
{
const expected_data = "expected";
const e = Error( "test e" );
const value = "resp data";
const res = _createMockResp();
const http = _createMockHttp( ( _, callback ) =>
{
callback( res );
res.trigger( 'data', expected_data );
res.trigger( 'end' );
} );
Sut.extend(
{
'override requestEnd'( given_res, data, callback )
{
expect( given_res ).to.equal( res );
expect( data ).to.equal( expected_data );
callback( e, value );
},
} )( { http: http }, _createMockUrl() )
.requestData( "", 'GET', '', ( given_e, given_data ) =>
{
expect( given_e ).to.equal( e );
expect( given_data ).to.equal( value );
done();
} );
} );
it( "allows overriding concept of success", done =>
{
const res = _createMockResp();
const http = _createMockHttp( ( _, callback ) =>
{
callback( res );
res.trigger( 'end' );
} );
// would normally be a failure
res.statusCode = 500;
Sut.extend(
{
'override isSuccessful': ( given_res ) => true,
} )( { http: http }, _createMockUrl() )
.requestData( "", 'GET', '', ( e ) =>
{
expect( e ).to.equal( null );
done();
} );
} );
it( "allows overriding error handling", done =>
{
const expected_e = Error( 'expected' );
const error = {};
const value = 'error data';
const http = _createMockHttp( ( _, callback ) =>
{
callback( _createMockResp() );
} );
Sut.extend(
{
'override serveError'(
given_e, given_res, given_data, callback
)
{
expect( given_e ).to.equal( expected_e );
expect( given_res ).to.equal( null );
expect( given_data ).to.equal( null );
error.e = given_e;
callback( error, value );
},
} )( { http: http }, _createMockUrl() )
.requestData( "", 'GET', '', ( e, given_value ) =>
{
expect( e ).to.equal( error );
expect( e.e ).to.equal( expected_e );
expect( given_value ).to.equal( value );
done();
} );
// we're still hanging the request since we haven't called the
// callback in http
http.req.trigger( 'error', expected_e );
} );
} );
} );
const _createMockHttp = req_callback =>
{
const events = {};
return Object.create( {
req: Object.create( {
written: '',
on( event, hook )
{
events[ event ] = hook;
},
trigger( event, data )
{
events[ event ]( data );
},
end()
{
// thunk defined by #request below
events.onend();
},
write( data )
{
this.written = data;
},
} ),
request( options, callback )
{
// not a real event; just for convenience
events.onend = () => req_callback( options, callback );
return this.req;
},
} );
};
const _createMockUrl = callback => ( {
parse: callback || ( () => ( { protocol: 'http:' } ) ),
} );
const _createMockResp = () => Object.create( {
event: {
data() {},
end() {},
},
statusCode: 200,
on( ev, hook )
{
this.event[ ev ] = hook;
},
trigger( ev, data )
{
this.event[ ev ]( data );
}
} );