apiTest.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690
  1. var should = require('chai').should();
  2. var request = require('request');
  3. var Q = require('q');
  4. var config = {
  5. "authorizedKeys": {
  6. "1234567890": "contact@gaelmetais.com"
  7. }
  8. };
  9. var serverUrl = 'http://localhost:8387';
  10. var wwwUrl = 'http://localhost:8388';
  11. describe('api', function() {
  12. var syncRunResultUrl;
  13. var asyncRunId;
  14. var screenshotUrl;
  15. it('should refuse a query with an invalid key', function(done) {
  16. this.timeout(5000);
  17. request({
  18. method: 'POST',
  19. url: serverUrl + '/api/runs',
  20. body: {
  21. url: wwwUrl + '/simple-page.html',
  22. waitForResponse: false
  23. },
  24. json: true,
  25. headers: {
  26. 'Content-Type': 'application/json',
  27. 'X-Api-Key': 'invalid'
  28. }
  29. }, function(error, response, body) {
  30. if (!error && response.statusCode === 401) {
  31. done();
  32. } else {
  33. done(error || response.statusCode);
  34. }
  35. });
  36. });
  37. it('should fail without an URL when asynchronous', function(done) {
  38. this.timeout(15000);
  39. request({
  40. method: 'POST',
  41. url: serverUrl + '/api/runs',
  42. body: {
  43. url: '',
  44. waitForResponse: true
  45. },
  46. json: true,
  47. headers: {
  48. 'Content-Type': 'application/json',
  49. 'X-Api-Key': Object.keys(config.authorizedKeys)[0]
  50. }
  51. }, function(error, response, body) {
  52. if (!error && response.statusCode === 400) {
  53. done();
  54. } else {
  55. done(error || response.statusCode);
  56. }
  57. });
  58. });
  59. it('should fail without an URL when synchronous', function(done) {
  60. this.timeout(15000);
  61. request({
  62. method: 'POST',
  63. url: serverUrl + '/api/runs',
  64. body: {
  65. url: '',
  66. waitForResponse: true
  67. },
  68. json: true,
  69. headers: {
  70. 'Content-Type': 'application/json',
  71. 'X-Api-Key': Object.keys(config.authorizedKeys)[0]
  72. }
  73. }, function(error, response, body) {
  74. if (!error && response.statusCode === 400) {
  75. done();
  76. } else {
  77. done(error || response.statusCode);
  78. }
  79. });
  80. });
  81. it('should launch a synchronous run', function(done) {
  82. this.timeout(15000);
  83. request({
  84. method: 'POST',
  85. url: serverUrl + '/api/runs',
  86. body: {
  87. url: wwwUrl + '/simple-page.html',
  88. waitForResponse: true,
  89. screenshot: true,
  90. device: 'tablet',
  91. //waitForSelector: '*',
  92. cookie: 'foo=bar;domain=google.com',
  93. authUser: 'joe',
  94. authPass: 'secret'
  95. },
  96. json: true,
  97. headers: {
  98. 'Content-Type': 'application/json',
  99. 'X-Api-Key': Object.keys(config.authorizedKeys)[0]
  100. }
  101. }, function(error, response, body) {
  102. if (!error && response.statusCode === 302) {
  103. response.headers.should.have.a.property('location').that.is.a('string');
  104. syncRunResultUrl = response.headers.location;
  105. done();
  106. } else {
  107. done(error || response.statusCode);
  108. }
  109. });
  110. });
  111. it('should return the rules only', function(done) {
  112. this.timeout(15000);
  113. request({
  114. method: 'POST',
  115. url: serverUrl + '/api/runs',
  116. body: {
  117. url: wwwUrl + '/simple-page.html',
  118. waitForResponse: true,
  119. partialResult: 'rules'
  120. },
  121. json: true,
  122. headers: {
  123. 'Content-Type': 'application/json',
  124. 'X-Api-Key': Object.keys(config.authorizedKeys)[0]
  125. }
  126. }, function(error, response, body) {
  127. if (!error && response.statusCode === 302) {
  128. response.headers.should.have.a.property('location').that.is.a('string');
  129. response.headers.location.should.contain('/rules');
  130. done();
  131. } else {
  132. done(error || response.statusCode);
  133. }
  134. });
  135. });
  136. it('should retrieve the results for the synchronous run', function(done) {
  137. this.timeout(15000);
  138. request({
  139. method: 'GET',
  140. url: serverUrl + syncRunResultUrl,
  141. json: true,
  142. }, function(error, response, body) {
  143. if (!error && response.statusCode === 200) {
  144. body.should.have.a.property('runId').that.is.a('string');
  145. body.should.have.a.property('params').that.is.an('object');
  146. body.should.have.a.property('scoreProfiles').that.is.an('object');
  147. body.should.have.a.property('rules').that.is.an('object');
  148. body.should.have.a.property('toolsResults').that.is.an('object');
  149. body.should.have.a.property('javascriptExecutionTree').that.is.an('object');
  150. body.javascriptExecutionTree.should.not.deep.equal({});
  151. // Check if settings are correctly sent and retrieved
  152. body.params.options.should.have.a.property('device').that.equals('tablet');
  153. //body.params.options.should.have.a.property('waitForSelector').that.equals('*');
  154. body.params.options.should.have.a.property('cookie').that.equals('foo=bar;domain=google.com');
  155. body.params.options.should.have.a.property('authUser').that.equals('joe');
  156. body.params.options.should.have.a.property('authPass').that.equals('secret');
  157. // Check if the screenshot temporary file was correctly removed
  158. body.params.options.should.not.have.a.property('screenshot');
  159. // Check if the screenshot buffer was correctly removed
  160. body.should.not.have.a.property('screenshotBuffer');
  161. // Check if the screenshot url is here
  162. body.should.have.a.property('screenshotUrl');
  163. body.screenshotUrl.should.equal('/api/results/' + body.runId + '/screenshot.jpg');
  164. screenshotUrl = body.screenshotUrl;
  165. done();
  166. } else {
  167. done(error || response.statusCode);
  168. }
  169. });
  170. });
  171. it('should launch a run without waiting for the response', function(done) {
  172. this.timeout(5000);
  173. request({
  174. method: 'POST',
  175. url: serverUrl + '/api/runs',
  176. body: {
  177. url: wwwUrl + '/simple-page.html',
  178. waitForResponse: false,
  179. jsTimeline: true
  180. },
  181. json: true,
  182. headers: {
  183. 'Content-Type': 'application/json',
  184. 'X-Api-Key': Object.keys(config.authorizedKeys)[0]
  185. }
  186. }, function(error, response, body) {
  187. if (!error && response.statusCode === 200) {
  188. asyncRunId = body.runId;
  189. asyncRunId.should.be.a('string');
  190. done();
  191. } else {
  192. done(error || response.statusCode);
  193. }
  194. });
  195. });
  196. it('should respond run status: running', function(done) {
  197. this.timeout(5000);
  198. request({
  199. method: 'GET',
  200. url: serverUrl + '/api/runs/' + asyncRunId,
  201. json: true,
  202. headers: {
  203. 'Content-Type': 'application/json',
  204. 'X-Api-Key': Object.keys(config.authorizedKeys)[0]
  205. }
  206. }, function(error, response, body) {
  207. if (!error && response.statusCode === 200) {
  208. body.runId.should.equal(asyncRunId);
  209. body.status.should.deep.equal({
  210. statusCode: 'running'
  211. });
  212. done();
  213. } else {
  214. done(error || response.statusCode);
  215. }
  216. });
  217. });
  218. it('should accept up to 10 anonymous runs to the API', function(done) {
  219. this.timeout(5000);
  220. function launchRun() {
  221. var deferred = Q.defer();
  222. request({
  223. method: 'POST',
  224. url: serverUrl + '/api/runs',
  225. body: {
  226. url: wwwUrl + '/simple-page.html',
  227. waitForResponse: false
  228. },
  229. json: true
  230. }, function(error, response, body) {
  231. lastRunId = body.runId;
  232. if (error) {
  233. deferred.reject(error);
  234. } else {
  235. deferred.resolve(response, body);
  236. }
  237. });
  238. return deferred.promise;
  239. }
  240. launchRun()
  241. .then(launchRun)
  242. .then(launchRun)
  243. .then(launchRun)
  244. .then(launchRun)
  245. .then(function(response, body) {
  246. // Here should still be ok
  247. response.statusCode.should.equal(200);
  248. launchRun()
  249. .then(launchRun)
  250. .then(launchRun)
  251. .then(launchRun)
  252. .then(launchRun)
  253. .then(launchRun)
  254. .then(function(response, body) {
  255. // It should fail now
  256. response.statusCode.should.equal(429);
  257. done();
  258. })
  259. .fail(function(error) {
  260. done(error);
  261. });
  262. }).fail(function(error) {
  263. done(error);
  264. });
  265. });
  266. it('should respond 404 to unknown runId', function(done) {
  267. this.timeout(5000);
  268. request({
  269. method: 'GET',
  270. url: serverUrl + '/api/runs/unknown',
  271. json: true
  272. }, function(error, response, body) {
  273. if (!error && response.statusCode === 404) {
  274. done();
  275. } else {
  276. done(error || response.statusCode);
  277. }
  278. });
  279. });
  280. it('should respond 404 to unknown result', function(done) {
  281. this.timeout(5000);
  282. request({
  283. method: 'GET',
  284. url: serverUrl + '/api/results/unknown',
  285. json: true
  286. }, function(error, response, body) {
  287. if (!error && response.statusCode === 404) {
  288. done();
  289. } else {
  290. done(error || response.statusCode);
  291. }
  292. });
  293. });
  294. it('should respond status complete to the first run', function(done) {
  295. this.timeout(12000);
  296. function checkStatus() {
  297. request({
  298. method: 'GET',
  299. url: serverUrl + '/api/runs/' + asyncRunId,
  300. json: true
  301. }, function(error, response, body) {
  302. if (!error && response.statusCode === 200) {
  303. body.runId.should.equal(asyncRunId);
  304. if (body.status.statusCode === 'running') {
  305. setTimeout(checkStatus, 250);
  306. } else if (body.status.statusCode === 'complete') {
  307. done();
  308. } else {
  309. done(body.status.statusCode);
  310. }
  311. } else {
  312. done(error || response.statusCode);
  313. }
  314. });
  315. }
  316. checkStatus();
  317. });
  318. it('should find the result of the async run', function(done) {
  319. this.timeout(5000);
  320. request({
  321. method: 'GET',
  322. url: serverUrl + '/api/results/' + asyncRunId,
  323. json: true,
  324. }, function(error, response, body) {
  325. if (!error && response.statusCode === 200) {
  326. body.should.have.a.property('runId').that.equals(asyncRunId);
  327. body.should.have.a.property('params').that.is.an('object');
  328. body.params.url.should.equal(wwwUrl + '/simple-page.html');
  329. body.should.have.a.property('scoreProfiles').that.is.an('object');
  330. body.scoreProfiles.should.have.a.property('generic').that.is.an('object');
  331. body.scoreProfiles.generic.should.have.a.property('globalScore').that.is.a('number');
  332. body.scoreProfiles.generic.should.have.a.property('categories').that.is.an('object');
  333. body.should.have.a.property('rules').that.is.an('object');
  334. body.should.have.a.property('toolsResults').that.is.an('object');
  335. body.toolsResults.should.have.a.property('phantomas').that.is.an('object');
  336. body.should.have.a.property('javascriptExecutionTree').that.is.an('object');
  337. body.javascriptExecutionTree.should.have.a.property('data').that.is.an('object');
  338. body.javascriptExecutionTree.data.should.have.a.property('type').that.equals('main');
  339. done();
  340. } else {
  341. done(error || response.statusCode);
  342. }
  343. });
  344. });
  345. it('should return the generic score object', function(done) {
  346. this.timeout(5000);
  347. request({
  348. method: 'GET',
  349. url: serverUrl + '/api/results/' + asyncRunId + '/generalScores',
  350. json: true,
  351. }, function(error, response, body) {
  352. if (!error && response.statusCode === 200) {
  353. body.should.have.a.property('globalScore').that.is.a('number');
  354. body.should.have.a.property('categories').that.is.an('object');
  355. done();
  356. } else {
  357. done(error || response.statusCode);
  358. }
  359. });
  360. });
  361. it('should return the generic score object also', function(done) {
  362. this.timeout(5000);
  363. request({
  364. method: 'GET',
  365. url: serverUrl + '/api/results/' + asyncRunId + '/generalScores/generic',
  366. json: true,
  367. }, function(error, response, body) {
  368. if (!error && response.statusCode === 200) {
  369. body.should.have.a.property('globalScore').that.is.a('number');
  370. body.should.have.a.property('categories').that.is.an('object');
  371. done();
  372. } else {
  373. done(error || response.statusCode);
  374. }
  375. });
  376. });
  377. it('should not find an unknown score object', function(done) {
  378. this.timeout(5000);
  379. request({
  380. method: 'GET',
  381. url: serverUrl + '/api/results/' + asyncRunId + '/generalScores/unknown',
  382. json: true,
  383. }, function(error, response, body) {
  384. if (!error && response.statusCode === 404) {
  385. done();
  386. } else {
  387. done(error || response.statusCode);
  388. }
  389. });
  390. });
  391. it('should return the rules', function(done) {
  392. this.timeout(5000);
  393. request({
  394. method: 'GET',
  395. url: serverUrl + '/api/results/' + asyncRunId + '/rules',
  396. json: true,
  397. }, function(error, response, body) {
  398. if (!error && response.statusCode === 200) {
  399. var firstRule = body[Object.keys(body)[0]];
  400. firstRule.should.have.a.property('policy').that.is.an('object');
  401. firstRule.should.have.a.property('value').that.is.a('number');
  402. firstRule.should.have.a.property('bad').that.is.a('boolean');
  403. firstRule.should.have.a.property('abnormal').that.is.a('boolean');
  404. firstRule.should.have.a.property('score').that.is.a('number');
  405. firstRule.should.have.a.property('abnormalityScore').that.is.a('number');
  406. done();
  407. } else {
  408. done(error || response.statusCode);
  409. }
  410. });
  411. });
  412. it('should return the javascript execution tree', function(done) {
  413. this.timeout(5000);
  414. request({
  415. method: 'GET',
  416. url: serverUrl + '/api/results/' + asyncRunId + '/javascriptExecutionTree',
  417. json: true,
  418. }, function(error, response, body) {
  419. if (!error && response.statusCode === 200) {
  420. body.should.have.a.property('data').that.is.an('object');
  421. body.data.should.have.a.property('type').that.equals('main');
  422. done();
  423. } else {
  424. done(error || response.statusCode);
  425. }
  426. });
  427. });
  428. it('should return the phantomas results', function(done) {
  429. this.timeout(5000);
  430. request({
  431. method: 'GET',
  432. url: serverUrl + '/api/results/' + asyncRunId + '/toolsResults/phantomas',
  433. json: true,
  434. }, function(error, response, body) {
  435. if (!error && response.statusCode === 200) {
  436. body.should.have.a.property('metrics').that.is.an('object');
  437. body.should.have.a.property('offenders').that.is.an('object');
  438. done();
  439. } else {
  440. done(error || response.statusCode);
  441. }
  442. });
  443. });
  444. it('should return the entire object and exclude toolsResults', function(done) {
  445. this.timeout(5000);
  446. request({
  447. method: 'GET',
  448. url: serverUrl + '/api/results/' + asyncRunId + '?exclude=toolsResults',
  449. json: true,
  450. }, function(error, response, body) {
  451. if (!error && response.statusCode === 200) {
  452. body.should.have.a.property('runId').that.equals(asyncRunId);
  453. body.should.have.a.property('params').that.is.an('object');
  454. body.should.have.a.property('scoreProfiles').that.is.an('object');
  455. body.should.have.a.property('rules').that.is.an('object');
  456. body.should.have.a.property('javascriptExecutionTree').that.is.an('object');
  457. body.should.not.have.a.property('toolsResults').that.is.an('object');
  458. done();
  459. } else {
  460. done(error || response.statusCode);
  461. }
  462. });
  463. });
  464. it('should return the entire object and exclude params and toolsResults', function(done) {
  465. this.timeout(5000);
  466. request({
  467. method: 'GET',
  468. url: serverUrl + '/api/results/' + asyncRunId + '?exclude=toolsResults,params',
  469. json: true,
  470. }, function(error, response, body) {
  471. if (!error && response.statusCode === 200) {
  472. body.should.have.a.property('runId').that.equals(asyncRunId);
  473. body.should.have.a.property('scoreProfiles').that.is.an('object');
  474. body.should.have.a.property('rules').that.is.an('object');
  475. body.should.have.a.property('javascriptExecutionTree').that.is.an('object');
  476. body.should.not.have.a.property('params').that.is.an('object');
  477. body.should.not.have.a.property('toolsResults').that.is.an('object');
  478. done();
  479. } else {
  480. done(error || response.statusCode);
  481. }
  482. });
  483. });
  484. it('should return the entire object and don\'t exclude anything', function(done) {
  485. this.timeout(5000);
  486. request({
  487. method: 'GET',
  488. url: serverUrl + '/api/results/' + asyncRunId + '?exclude=',
  489. json: true,
  490. }, function(error, response, body) {
  491. if (!error && response.statusCode === 200) {
  492. body.should.have.a.property('runId').that.equals(asyncRunId);
  493. body.should.have.a.property('scoreProfiles').that.is.an('object');
  494. body.should.have.a.property('rules').that.is.an('object');
  495. body.should.have.a.property('javascriptExecutionTree').that.is.an('object');
  496. body.should.have.a.property('params').that.is.an('object');
  497. body.should.have.a.property('toolsResults').that.is.an('object');
  498. done();
  499. } else {
  500. done(error || response.statusCode);
  501. }
  502. });
  503. });
  504. it('should return the entire object and don\'t exclude anything', function(done) {
  505. this.timeout(5000);
  506. request({
  507. method: 'GET',
  508. url: serverUrl + '/api/results/' + asyncRunId + '?exclude=null',
  509. json: true,
  510. }, function(error, response, body) {
  511. if (!error && response.statusCode === 200) {
  512. body.should.have.a.property('runId').that.equals(asyncRunId);
  513. body.should.have.a.property('scoreProfiles').that.is.an('object');
  514. body.should.have.a.property('rules').that.is.an('object');
  515. body.should.have.a.property('javascriptExecutionTree').that.is.an('object');
  516. body.should.have.a.property('params').that.is.an('object');
  517. body.should.have.a.property('toolsResults').that.is.an('object');
  518. done();
  519. } else {
  520. done(error || response.statusCode);
  521. }
  522. });
  523. });
  524. it('should retrieve the screenshot', function(done) {
  525. this.timeout(5000);
  526. request({
  527. method: 'GET',
  528. url: serverUrl + screenshotUrl
  529. }, function(error, response, body) {
  530. if (!error && response.statusCode === 200) {
  531. response.headers['content-type'].should.equal('image/jpeg');
  532. done();
  533. } else {
  534. done(error || response.statusCode);
  535. }
  536. });
  537. });
  538. it('should fail on a unexistant screenshot', function(done) {
  539. this.timeout(5000);
  540. request({
  541. method: 'GET',
  542. url: serverUrl + '/api/results/000000/screenshot.jpg'
  543. }, function(error, response, body) {
  544. if (!error && response.statusCode === 404) {
  545. done();
  546. } else {
  547. done(error || response.statusCode);
  548. }
  549. });
  550. });
  551. });