400 lines
12 KiB
JavaScript
400 lines
12 KiB
JavaScript
/**
|
|
* Test case for XMLHttpRequest HTTP protocol implementation
|
|
*
|
|
* Copyright (C) 2014, 2015 LoVullo Associates, Inc.
|
|
*
|
|
* 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/>.
|
|
*/
|
|
|
|
var dapi = require( '../../../' ).dapi,
|
|
expect = require( 'chai' ).expect,
|
|
Class = require( 'easejs' ).Class,
|
|
HttpImpl = dapi.http.HttpImpl,
|
|
Sut = dapi.http.XhrHttpImpl,
|
|
|
|
DummyXhr = function()
|
|
{
|
|
this.open = function()
|
|
{
|
|
DummyXhr.args = arguments;
|
|
};
|
|
};
|
|
|
|
|
|
describe( 'XhrHttpImpl', function()
|
|
{
|
|
/**
|
|
* Since ECMAScript does not have return typing, we won't know if the ctor
|
|
* actually returns an XMLHttpRequest until we try.
|
|
*/
|
|
it( 'will accept any constructor', function()
|
|
{
|
|
expect( function()
|
|
{
|
|
Sut( function() {} );
|
|
} ).to.not.throw( Error );
|
|
} );
|
|
|
|
|
|
it( 'is an HttpImpl', function()
|
|
{
|
|
var sut = Sut( function() {} );
|
|
expect( Class.isA( HttpImpl, sut ) ).to.be.ok;
|
|
} );
|
|
|
|
|
|
describe( '.requestData', function()
|
|
{
|
|
it( 'requests a connection using the given method', function()
|
|
{
|
|
var method = 'GET',
|
|
sut = Sut( DummyXhr );
|
|
|
|
sut.requestData( 'http://foo', method, "", function() {} );
|
|
|
|
var args = DummyXhr.args;
|
|
expect( args[ 0 ] ).to.equal( method );
|
|
} );
|
|
|
|
|
|
/**
|
|
* Since the request is asynchronous, we should be polite and not return
|
|
* errors in two different formats; we will catch it and instead pass it
|
|
* back via the callback.
|
|
*/
|
|
it( 'returns XHR open() errors via callback', function( done )
|
|
{
|
|
var e = Error( "Test error" ),
|
|
Xhr = function()
|
|
{
|
|
this.open = function()
|
|
{
|
|
throw e;
|
|
};
|
|
};
|
|
|
|
// should not throw an exception
|
|
expect( function()
|
|
{
|
|
// should instead provide to callback
|
|
Sut( Xhr )
|
|
.requestData( 'http://foo', 'GET', "", function( err, data )
|
|
{
|
|
expect( err ).to.equal( e );
|
|
expect( data ).to.equal( null );
|
|
done();
|
|
} );
|
|
} ).to.not.throw( Error );
|
|
} );
|
|
|
|
|
|
it( 'returns XHR response via callback when no error', function( done )
|
|
{
|
|
var retdata = "foobar",
|
|
StubXhr = createStubXhr();
|
|
|
|
StubXhr.prototype.responseText = retdata;
|
|
StubXhr.prototype.readyState = 4; // done
|
|
StubXhr.prototype.status = 200; // OK
|
|
|
|
Sut( StubXhr )
|
|
.requestData( 'http://bar', 'GET', "", function( err, resp )
|
|
{
|
|
expect( err ).to.equal( null );
|
|
expect( resp ).to.equal( retdata );
|
|
done();
|
|
} );
|
|
} );
|
|
|
|
|
|
describe( 'HTTP method is GET', function()
|
|
{
|
|
it( 'appends data to URL', function( done )
|
|
{
|
|
var url = 'http://bar',
|
|
src = "moocow%foocow%",
|
|
StubXhr = createStubXhr();
|
|
|
|
StubXhr.prototype.readyState = 4; // done
|
|
StubXhr.prototype.status = 200; // OK
|
|
|
|
StubXhr.prototype.open = function( _, given_url )
|
|
{
|
|
// no additional encoding should be performed; it's
|
|
// assumed to have already been done
|
|
expect( given_url ).to.equal(
|
|
url + '?' + src
|
|
);
|
|
};
|
|
|
|
StubXhr.prototype.send = function( data )
|
|
{
|
|
// no posting on GET
|
|
expect( data ).is.equal( undefined );
|
|
StubXhr.inst.onreadystatechange();
|
|
};
|
|
|
|
Sut( StubXhr )
|
|
.requestData( url, 'GET', src, done );
|
|
} );
|
|
|
|
|
|
it( 'leaves URL unaltered when data is empty', function( done )
|
|
{
|
|
var url = 'http://bar',
|
|
StubXhr = createStubXhr();
|
|
|
|
StubXhr.prototype.readyState = 4; // done
|
|
StubXhr.prototype.status = 200; // OK
|
|
|
|
StubXhr.prototype.open = function( _, given_url )
|
|
{
|
|
// unaltered
|
|
expect( given_url ).to.equal( url );
|
|
};
|
|
|
|
Sut( StubXhr )
|
|
.requestData( url, 'GET', "", done );
|
|
} );
|
|
|
|
|
|
it( 'does not set Content-Type', function( done )
|
|
{
|
|
var url = 'http://bar',
|
|
StubXhr = createStubXhr();
|
|
|
|
StubXhr.prototype.readyState = 4; // done
|
|
StubXhr.prototype.status = 200; // OK
|
|
|
|
StubXhr.prototype.setRequestHeader = function()
|
|
{
|
|
// warning: this is fragile, if additional headers are
|
|
// ever set
|
|
throw Error( 'Headers should not be set on GET' );
|
|
};
|
|
|
|
Sut( StubXhr )
|
|
.requestData( url, 'GET', "", done );
|
|
} );
|
|
} );
|
|
|
|
|
|
describe( 'HTTP method is not GET', function()
|
|
{
|
|
it( 'sends data verbatim as x-www-form-urlencoded', function( done )
|
|
{
|
|
var url = 'http://bar',
|
|
src = "moocow",
|
|
StubXhr = createStubXhr(),
|
|
|
|
open_called = false,
|
|
send_called = false,
|
|
header_called = false;
|
|
|
|
StubXhr.prototype.readyState = 4; // done
|
|
StubXhr.prototype.status = 200; // OK
|
|
|
|
StubXhr.prototype.open = function( _, given_url )
|
|
{
|
|
open_called = true;
|
|
|
|
// URL should be unchanged
|
|
expect( given_url ).to.equal( url );
|
|
};
|
|
|
|
StubXhr.prototype.send = function( data )
|
|
{
|
|
send_called = true;
|
|
|
|
expect( data ).is.equal( src );
|
|
StubXhr.inst.onreadystatechange();
|
|
};
|
|
|
|
StubXhr.prototype.setRequestHeader = function( name, val )
|
|
{
|
|
header_called = true;
|
|
|
|
// warning: this is fragile, if additional headers are
|
|
// ever set
|
|
expect( name ).to.equal( 'Content-Type' );
|
|
expect( val ).to.equal(
|
|
'application/x-www-form-urlencoded'
|
|
);
|
|
};
|
|
|
|
Sut( StubXhr )
|
|
.requestData( url, 'POST', src, function()
|
|
{
|
|
expect( open_called && send_called && header_called )
|
|
.to.be.true;
|
|
done();
|
|
} );
|
|
} );
|
|
} );
|
|
|
|
|
|
describe( 'if return status code is not successful', function()
|
|
{
|
|
/**
|
|
* This is the default behavior, but can be changed by overriding
|
|
* the onLoad method.
|
|
*/
|
|
it( 'returns error to callback with status code', function( done )
|
|
{
|
|
var StubXhr = createStubXhr();
|
|
StubXhr.prototype.status = 404;
|
|
|
|
Sut( StubXhr )
|
|
.requestData( 'http://foo', 'GET', '', function( err, _ )
|
|
{
|
|
expect( err ).to.be.instanceOf( Error );
|
|
|
|
expect( err.message ).to.contain(
|
|
StubXhr.prototype.status
|
|
);
|
|
|
|
expect( err.status ).to.equal(
|
|
StubXhr.prototype.status
|
|
);
|
|
|
|
done();
|
|
} );
|
|
} );
|
|
|
|
|
|
it( 'returns response text as output', function( done )
|
|
{
|
|
var StubXhr = createStubXhr(),
|
|
status = 404,
|
|
reply = 'foobunny';
|
|
|
|
StubXhr.prototype.responseText = reply;
|
|
|
|
Sut( StubXhr )
|
|
.requestData( 'http://foo', 'GET', '', function( _, resp )
|
|
{
|
|
expect( resp ).to.equal( reply );
|
|
done();
|
|
} );
|
|
} );
|
|
} );
|
|
|
|
|
|
it( 'considers any 2xx status to be successful', function( done )
|
|
{
|
|
var StubXhr = createStubXhr();
|
|
StubXhr.prototype.status = 250;
|
|
|
|
Sut( StubXhr )
|
|
.requestData( 'http://foo', 'GET', '', function( err, _ )
|
|
{
|
|
expect( err ).to.equal( null );
|
|
done();
|
|
} );
|
|
} );
|
|
|
|
|
|
it( 'allows overriding notion of success/failure', function( done )
|
|
{
|
|
var chk = 12345;
|
|
|
|
// succeed on CHK
|
|
var StubXhr = createStubXhr();
|
|
StubXhr.prototype.status = chk;
|
|
|
|
Sut.extend(
|
|
{
|
|
'override protected isSuccessful': function( status )
|
|
{
|
|
return status === chk;
|
|
},
|
|
} )( StubXhr )
|
|
.requestData( 'http://foo', 'GET', '', function( err, resp )
|
|
{
|
|
expect( err ).to.equal( null );
|
|
done();
|
|
} );
|
|
} );
|
|
|
|
|
|
it( 'allows customizing error', function( done )
|
|
{
|
|
var _self = this,
|
|
chk = {};
|
|
|
|
var StubXhr = createStubXhr();
|
|
StubXhr.prototype.status = 404;
|
|
|
|
Sut.extend(
|
|
{
|
|
'override protected serveError': function( req, callback )
|
|
{
|
|
var e = Error( 'foobunny' );
|
|
e.foo = true;
|
|
|
|
expect( req ).to.be.an.instanceOf( StubXhr );
|
|
|
|
callback( e, chk );
|
|
},
|
|
} )( StubXhr )
|
|
.requestData( 'http://foo', 'GET', '', function( err, resp )
|
|
{
|
|
expect( ( err || {} ).foo ).to.be.ok;
|
|
expect( resp ).to.equal( chk );
|
|
|
|
done();
|
|
} );
|
|
} );
|
|
|
|
|
|
it( 'returns self', function()
|
|
{
|
|
var sut = Sut( function() {} ),
|
|
ret = sut.requestData(
|
|
'http://foo', 'GET', "", function() {}
|
|
);
|
|
|
|
expect( ret ).to.equal( sut );
|
|
} );
|
|
} );
|
|
} );
|
|
|
|
|
|
function createStubXhr()
|
|
{
|
|
var StubXhr = function()
|
|
{
|
|
StubXhr.inst = this;
|
|
};
|
|
|
|
StubXhr.prototype = {
|
|
onreadystatechange: null,
|
|
responseText: '',
|
|
readyState: 4, // don,
|
|
status: 200, // O,
|
|
|
|
open: function() {},
|
|
send: function( data )
|
|
{
|
|
this.onreadystatechange();
|
|
},
|
|
setRequestHeader: function() {},
|
|
};
|
|
|
|
return StubXhr;
|
|
}
|
|
|