apiTest.js 22 KB

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