2017-08-19 10:57:54 +00:00
describe ( "Zotero.HTTP" , function ( ) {
2019-04-23 23:49:48 +00:00
var server ;
2017-08-19 10:57:54 +00:00
var httpd ;
var port = 16213 ;
2018-09-11 05:23:15 +00:00
var baseURL = ` http://127.0.0.1: ${ port } / `
var testURL = baseURL + 'test.html' ;
var redirectLocation = baseURL + 'test2.html' ;
2017-08-19 10:57:54 +00:00
2019-04-23 23:49:48 +00:00
function setResponse ( response ) {
setHTTPResponse ( server , baseURL , response , { } ) ;
}
2017-08-19 10:57:54 +00:00
before ( function * ( ) {
2019-04-23 23:49:48 +00:00
// Real HTTP server
2017-08-19 10:57:54 +00:00
Components . utils . import ( "resource://zotero-unit/httpd.js" ) ;
httpd = new HttpServer ( ) ;
httpd . start ( port ) ;
httpd . registerPathHandler (
'/test.html' ,
{
handle : function ( request , response ) {
response . setStatusLine ( null , 200 , "OK" ) ;
response . write ( "<html><body><p>Test</p><p>Test 2</p></body></html>" ) ;
}
}
) ;
2018-09-11 05:23:15 +00:00
httpd . registerPathHandler (
'/redirect' ,
{
handle : function ( request , response ) {
response . setHeader ( 'Location' , redirectLocation ) ;
response . setStatusLine ( null , 301 , "Moved Permanently" ) ;
response . write ( ` <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN"> \n <html><head> \n <title>301 Moved Permanently</title> \n </head><body> \n <h1>Moved Permanently</h1> \n <p>The document has moved <a href=" ${ redirectLocation } ">here</a>.</p> \n </body></html> ` ) ;
}
}
) ;
2018-10-04 02:24:39 +00:00
httpd . registerPathHandler (
'/test-redirect.html' ,
{
handle : function ( request , response ) {
response . setHeader ( "Content-Type" , 'text/html' , false ) ;
response . setStatusLine ( null , 200 , "OK" ) ;
response . write ( "<html><head><meta http-equiv=\"refresh\" content=\"2;url=test.html\"/></head><body></body></html>" ) ;
}
}
) ;
2022-12-23 01:47:28 +00:00
httpd . registerPathHandler (
'/requireJSON' ,
{
handle ( request , response ) {
if ( request . getHeader ( 'Content-Type' ) == 'application/json' ) {
response . setStatusLine ( null , 200 , "OK" ) ;
}
else {
response . setStatusLine ( null , 400 , "Bad Request" ) ;
}
response . write ( 'JSON required' ) ;
}
}
) ;
2017-08-19 10:57:54 +00:00
} ) ;
2019-04-23 23:49:48 +00:00
beforeEach ( function ( ) {
// Fake XHR, which can be disabled per test with `Zotero.HTTP.mock = null`
Zotero . HTTP . mock = sinon . FakeXMLHttpRequest ;
server = sinon . fakeServer . create ( ) ;
server . autoRespond = true ;
} ) ;
2019-12-23 09:29:41 +00:00
afterEach ( async function ( ) {
// Allow requests to settle
await Zotero . Promise . delay ( 50 ) ;
} ) ;
2017-08-19 10:57:54 +00:00
after ( function * ( ) {
var defer = new Zotero . Promise . defer ( ) ;
httpd . stop ( ( ) => defer . resolve ( ) ) ;
yield defer . promise ;
2019-04-23 23:49:48 +00:00
Zotero . HTTP . mock = null ;
2017-08-19 10:57:54 +00:00
} ) ;
2018-09-11 05:23:15 +00:00
describe ( "#request()" , function ( ) {
it ( "should succeed with 3xx status if followRedirects is false" , async function ( ) {
2019-04-23 23:49:48 +00:00
Zotero . HTTP . mock = null ;
2018-09-11 05:23:15 +00:00
var req = await Zotero . HTTP . request (
'GET' ,
baseURL + 'redirect' ,
{
followRedirects : false
}
) ;
assert . equal ( req . status , 301 ) ;
assert . equal ( req . getResponseHeader ( 'Location' ) , redirectLocation ) ;
} ) ;
2019-04-23 23:49:48 +00:00
it ( "should catch an interrupted connection" , async function ( ) {
setResponse ( {
method : "GET" ,
url : "empty" ,
status : 0 ,
text : ""
} )
var e = await getPromiseError ( Zotero . HTTP . request ( "GET" , baseURL + "empty" ) ) ;
assert . ok ( e ) ;
assert . equal ( e . message , Zotero . getString ( 'sync.error.checkConnection' ) ) ;
} ) ;
it ( "should provide cancellerReceiver with a callback to cancel a request" , async function ( ) {
var timeoutID ;
server . autoRespond = false ;
server . respondWith ( function ( req ) {
if ( req . method == "GET" && req . url == baseURL + "slow" ) {
req . respond (
200 ,
{ } ,
"OK"
) ;
}
} ) ;
setTimeout ( function ( ) {
cancel ( ) ;
} , 50 ) ;
var e = await getPromiseError ( Zotero . HTTP . request (
"GET" ,
baseURL + "slow" ,
{
cancellerReceiver : function ( f ) {
cancel = f ;
}
}
) ) ;
assert . instanceOf ( e , Zotero . HTTP . CancelledException ) ;
server . respond ( ) ;
} ) ;
2022-12-23 01:47:28 +00:00
it ( "should process headers case insensitively" , async function ( ) {
Zotero . HTTP . mock = null ;
var req = await Zotero . HTTP . request (
'GET' ,
baseURL + 'requireJSON' ,
{
headers : {
'content-type' : 'application/json'
}
}
) ;
assert . equal ( req . status , 200 ) ;
} ) ;
2019-04-23 23:49:48 +00:00
describe ( "Retries" , function ( ) {
var spy ;
var delayStub ;
beforeEach ( function ( ) {
delayStub = sinon . stub ( Zotero . Promise , "delay" ) . returns ( Zotero . Promise . resolve ( ) ) ;
} ) ;
afterEach ( function ( ) {
if ( spy ) {
spy . restore ( ) ;
}
delayStub . restore ( ) ;
} ) ;
after ( function ( ) {
sinon . restore ( ) ;
} ) ;
it ( "should retry on 500 error" , async function ( ) {
setResponse ( {
method : "GET" ,
url : "error" ,
status : 500 ,
text : ""
} ) ;
spy = sinon . spy ( Zotero . HTTP , "_requestInternal" ) ;
var e = await getPromiseError (
Zotero . HTTP . request (
"GET" ,
baseURL + "error" ,
{
2020-12-11 07:04:48 +00:00
errorDelayIntervals : [ 10 , 20 , 100 ] ,
errorDelayMax : 35
2019-04-23 23:49:48 +00:00
}
)
) ;
assert . instanceOf ( e , Zotero . HTTP . UnexpectedStatusException ) ;
2020-12-11 07:04:48 +00:00
assert . isTrue ( spy . calledThrice ) ;
assert . isTrue ( delayStub . calledTwice ) ;
assert . equal ( delayStub . args [ 0 ] [ 0 ] , 10 ) ;
assert . equal ( delayStub . args [ 1 ] [ 0 ] , 20 ) ;
2019-04-23 23:49:48 +00:00
} ) ;
2022-08-29 21:35:06 +00:00
it ( "shouldn't retry on 500 error if errorDelayMax=0" , async function ( ) {
setResponse ( {
method : "GET" ,
url : "error" ,
status : 500 ,
text : ""
} ) ;
spy = sinon . spy ( Zotero . HTTP , "_requestInternal" ) ;
var e = await getPromiseError (
Zotero . HTTP . request (
"GET" ,
baseURL + "error" ,
{
errorDelayIntervals : [ 10 , 20 , 100 ] ,
errorDelayMax : 0
}
)
) ;
assert . instanceOf ( e , Zotero . HTTP . UnexpectedStatusException ) ;
assert . isTrue ( spy . calledOnce ) ;
assert . isTrue ( delayStub . notCalled ) ;
} ) ;
2019-04-23 23:49:48 +00:00
it ( "should provide cancellerReceiver a callback to cancel while waiting to retry a 5xx error" , async function ( ) {
delayStub . restore ( ) ;
setResponse ( {
method : "GET" ,
url : "error" ,
status : 500 ,
text : ""
} ) ;
var cancel ;
spy = sinon . spy ( Zotero . HTTP , "_requestInternal" ) ;
setTimeout ( ( ) => {
cancel ( ) ;
2021-08-27 21:12:28 +00:00
} , 300 ) ;
2019-04-23 23:49:48 +00:00
var e = await getPromiseError (
Zotero . HTTP . request (
"GET" ,
baseURL + "error" ,
{
2021-08-27 21:12:28 +00:00
errorDelayIntervals : [ 10 , 10 , 600 ] ,
2019-04-23 23:49:48 +00:00
cancellerReceiver : function ( ) {
cancel = arguments [ 0 ] ;
}
}
)
) ;
assert . instanceOf ( e , Zotero . HTTP . CancelledException ) ;
2019-12-23 09:29:41 +00:00
assert . equal ( spy . callCount , 3 ) ;
2019-04-23 23:49:48 +00:00
} ) ;
it ( "should obey Retry-After for 503" , function * ( ) {
var called = 0 ;
server . respond ( function ( req ) {
if ( req . method == "GET" && req . url == baseURL + "error" ) {
if ( called < 1 ) {
req . respond (
503 ,
{
"Retry-After" : "5"
} ,
""
) ;
}
else if ( called < 2 ) {
req . respond (
503 ,
{
"Retry-After" : "10"
} ,
""
) ;
}
else {
req . respond (
200 ,
{ } ,
""
) ;
}
}
called ++ ;
} ) ;
spy = sinon . spy ( Zotero . HTTP , "_requestInternal" ) ;
yield Zotero . HTTP . request ( "GET" , baseURL + "error" ) ;
2019-12-23 09:29:41 +00:00
assert . equal ( 3 , spy . callCount ) ;
2019-04-23 23:49:48 +00:00
// DEBUG: Why are these slightly off?
assert . approximately ( delayStub . args [ 0 ] [ 0 ] , 5 * 1000 , 5 ) ;
assert . approximately ( delayStub . args [ 1 ] [ 0 ] , 10 * 1000 , 5 ) ;
} ) ;
} ) ;
2018-09-11 05:23:15 +00:00
} ) ;
2017-08-19 10:57:54 +00:00
describe ( "#processDocuments()" , function ( ) {
2019-04-23 23:49:48 +00:00
beforeEach ( function ( ) {
Zotero . HTTP . mock = null ;
} ) ;
2017-08-19 10:57:54 +00:00
it ( "should provide a document object" , function * ( ) {
var called = false ;
yield Zotero . HTTP . processDocuments (
2018-09-11 05:23:15 +00:00
testURL ,
2017-08-19 10:57:54 +00:00
function ( doc ) {
2018-09-11 05:23:15 +00:00
assert . equal ( doc . location . href , testURL ) ;
2017-08-19 10:57:54 +00:00
assert . equal ( doc . querySelector ( 'p' ) . textContent , 'Test' ) ;
var p = doc . evaluate ( '//p' , doc , null , XPathResult . ANY _TYPE , null ) . iterateNext ( ) ;
assert . equal ( p . textContent , 'Test' ) ;
called = true ;
}
) ;
assert . isTrue ( called ) ;
} ) ;
2018-10-04 02:24:39 +00:00
it ( "should follow meta redirect for a document" , async function ( ) {
let url1 = ` http://127.0.0.1: ${ port } /test-redirect.html ` ;
let url2 = ` http://127.0.0.1: ${ port } /test.html ` ;
let called = false ;
await Zotero . HTTP . processDocuments (
url1 ,
function ( doc ) {
assert . equal ( doc . location . href , url2 ) ;
called = true ;
}
) ;
assert . isTrue ( called ) ;
} ) ;
2017-08-19 10:57:54 +00:00
} ) ;
describe ( "#loadDocuments()" , function ( ) {
var win ;
before ( function * ( ) {
// TEMP: createHiddenBrowser currently needs a parent window
win = yield loadBrowserWindow ( ) ;
} ) ;
after ( function * ( ) {
win . close ( ) ;
} ) ;
it ( "should provide a document object" , function * ( ) {
var called = false ;
yield new Zotero . Promise ( ( resolve ) => {
Zotero . HTTP . loadDocuments (
2018-09-11 05:23:15 +00:00
testURL ,
2017-08-19 10:57:54 +00:00
function ( doc ) {
2018-09-11 05:23:15 +00:00
assert . equal ( doc . location . href , testURL ) ;
2017-08-19 10:57:54 +00:00
assert . equal ( doc . querySelector ( 'p' ) . textContent , 'Test' ) ;
var p = doc . evaluate ( '//p' , doc , null , XPathResult . ANY _TYPE , null ) . iterateNext ( ) ;
assert . equal ( p . textContent , 'Test' ) ;
called = true ;
} ,
resolve
) ;
} ) ;
assert . isTrue ( called ) ;
} ) ;
2020-10-24 04:00:50 +00:00
it ( "should fail on non-2xx response" , async function ( ) {
var e = await getPromiseError ( new Zotero . Promise ( ( resolve , reject ) => {
Zotero . HTTP . loadDocuments (
baseURL + "nonexistent" ,
( ) => { } ,
resolve ,
reject
) ;
} ) ) ;
assert . instanceOf ( e , Zotero . HTTP . UnexpectedStatusException ) ;
} ) ;
2017-08-19 10:57:54 +00:00
} ) ;
} ) ;