apiTest.js 22 KB

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