apiTest.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567
  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. },
  87. json: true,
  88. headers: {
  89. 'X-Api-Key': Object.keys(config.authorizedKeys)[0]
  90. }
  91. }, function(error, response, body) {
  92. if (!error && response.statusCode === 302) {
  93. response.headers.should.have.a.property('location').that.is.a('string');
  94. syncRunResultUrl = response.headers.location;
  95. done();
  96. } else {
  97. done(error || response.statusCode);
  98. }
  99. });
  100. });
  101. it('should return the rules only', function(done) {
  102. this.timeout(15000);
  103. request({
  104. method: 'POST',
  105. url: serverUrl + '/api/runs',
  106. body: {
  107. url: wwwUrl + '/simple-page.html',
  108. waitForResponse: true,
  109. partialResult: 'rules'
  110. },
  111. json: true,
  112. headers: {
  113. 'X-Api-Key': Object.keys(config.authorizedKeys)[0]
  114. }
  115. }, function(error, response, body) {
  116. if (!error && response.statusCode === 302) {
  117. response.headers.should.have.a.property('location').that.is.a('string');
  118. response.headers.location.should.contain('/rules');
  119. done();
  120. } else {
  121. done(error || response.statusCode);
  122. }
  123. });
  124. });
  125. it('should retrieve the results for the synchronous run', function(done) {
  126. this.timeout(15000);
  127. request({
  128. method: 'GET',
  129. url: serverUrl + syncRunResultUrl,
  130. json: true,
  131. }, function(error, response, body) {
  132. if (!error && response.statusCode === 200) {
  133. body.should.have.a.property('runId').that.is.a('string');
  134. body.should.have.a.property('params').that.is.an('object');
  135. body.should.have.a.property('scoreProfiles').that.is.an('object');
  136. body.should.have.a.property('rules').that.is.an('object');
  137. body.should.have.a.property('toolsResults').that.is.an('object');
  138. // javascriptExecutionTree should only be filled if option jsTimeline is true
  139. body.should.have.a.property('javascriptExecutionTree').that.is.an('object');
  140. body.javascriptExecutionTree.should.deep.equal({});
  141. // Check if the screenshot temporary file was correctly removed
  142. body.params.options.should.not.have.a.property('screenshot');
  143. // Check if the screenshot buffer was correctly removed
  144. body.should.not.have.a.property('screenshotBuffer');
  145. // Check if the screenshot url is here
  146. body.should.have.a.property('screenshotUrl');
  147. body.screenshotUrl.should.equal('/api/results/' + body.runId + '/screenshot.jpg');
  148. screenshotUrl = body.screenshotUrl;
  149. done();
  150. } else {
  151. done(error || response.statusCode);
  152. }
  153. });
  154. });
  155. it('should launch a run without waiting for the response', function(done) {
  156. this.timeout(5000);
  157. request({
  158. method: 'POST',
  159. url: serverUrl + '/api/runs',
  160. body: {
  161. url: wwwUrl + '/simple-page.html',
  162. waitForResponse: false,
  163. jsTimeline: true
  164. },
  165. json: true,
  166. headers: {
  167. 'X-Api-Key': Object.keys(config.authorizedKeys)[0]
  168. }
  169. }, function(error, response, body) {
  170. if (!error && response.statusCode === 200) {
  171. asyncRunId = body.runId;
  172. asyncRunId.should.be.a('string');
  173. done();
  174. } else {
  175. done(error || response.statusCode);
  176. }
  177. });
  178. });
  179. it('should respond run status: running', function(done) {
  180. this.timeout(5000);
  181. request({
  182. method: 'GET',
  183. url: serverUrl + '/api/runs/' + asyncRunId,
  184. json: true,
  185. headers: {
  186. 'X-Api-Key': Object.keys(config.authorizedKeys)[0]
  187. }
  188. }, function(error, response, body) {
  189. if (!error && response.statusCode === 200) {
  190. body.runId.should.equal(asyncRunId);
  191. body.status.should.deep.equal({
  192. statusCode: 'running'
  193. });
  194. done();
  195. } else {
  196. done(error || response.statusCode);
  197. }
  198. });
  199. });
  200. it('should accept up to 10 anonymous runs to the API', function(done) {
  201. this.timeout(5000);
  202. function launchRun() {
  203. var deferred = Q.defer();
  204. request({
  205. method: 'POST',
  206. url: serverUrl + '/api/runs',
  207. body: {
  208. url: wwwUrl + '/simple-page.html',
  209. waitForResponse: false
  210. },
  211. json: true
  212. }, function(error, response, body) {
  213. lastRunId = body.runId;
  214. if (error) {
  215. deferred.reject(error);
  216. } else {
  217. deferred.resolve(response, body);
  218. }
  219. });
  220. return deferred.promise;
  221. }
  222. launchRun()
  223. .then(launchRun)
  224. .then(launchRun)
  225. .then(launchRun)
  226. .then(launchRun)
  227. .then(function(response, body) {
  228. // Here should still be ok
  229. response.statusCode.should.equal(200);
  230. launchRun()
  231. .then(launchRun)
  232. .then(launchRun)
  233. .then(launchRun)
  234. .then(launchRun)
  235. .then(launchRun)
  236. .then(function(response, body) {
  237. // It should fail now
  238. response.statusCode.should.equal(429);
  239. done();
  240. })
  241. .fail(function(error) {
  242. done(error);
  243. });
  244. }).fail(function(error) {
  245. done(error);
  246. });
  247. });
  248. it('should respond 404 to unknown runId', function(done) {
  249. this.timeout(5000);
  250. request({
  251. method: 'GET',
  252. url: serverUrl + '/api/runs/unknown',
  253. json: true
  254. }, function(error, response, body) {
  255. if (!error && response.statusCode === 404) {
  256. done();
  257. } else {
  258. done(error || response.statusCode);
  259. }
  260. });
  261. });
  262. it('should respond 404 to unknown result', function(done) {
  263. this.timeout(5000);
  264. request({
  265. method: 'GET',
  266. url: serverUrl + '/api/results/unknown',
  267. json: true
  268. }, function(error, response, body) {
  269. if (!error && response.statusCode === 404) {
  270. done();
  271. } else {
  272. done(error || response.statusCode);
  273. }
  274. });
  275. });
  276. it('should respond status complete to the first run', function(done) {
  277. this.timeout(12000);
  278. function checkStatus() {
  279. request({
  280. method: 'GET',
  281. url: serverUrl + '/api/runs/' + asyncRunId,
  282. json: true
  283. }, function(error, response, body) {
  284. if (!error && response.statusCode === 200) {
  285. body.runId.should.equal(asyncRunId);
  286. if (body.status.statusCode === 'running') {
  287. setTimeout(checkStatus, 250);
  288. } else if (body.status.statusCode === 'complete') {
  289. done();
  290. } else {
  291. done(body.status.statusCode);
  292. }
  293. } else {
  294. done(error || response.statusCode);
  295. }
  296. });
  297. }
  298. checkStatus();
  299. });
  300. it('should find the result of the async run', function(done) {
  301. this.timeout(5000);
  302. request({
  303. method: 'GET',
  304. url: serverUrl + '/api/results/' + asyncRunId,
  305. json: true,
  306. }, function(error, response, body) {
  307. if (!error && response.statusCode === 200) {
  308. body.should.have.a.property('runId').that.equals(asyncRunId);
  309. body.should.have.a.property('params').that.is.an('object');
  310. body.params.url.should.equal(wwwUrl + '/simple-page.html');
  311. body.should.have.a.property('scoreProfiles').that.is.an('object');
  312. body.scoreProfiles.should.have.a.property('generic').that.is.an('object');
  313. body.scoreProfiles.generic.should.have.a.property('globalScore').that.is.a('number');
  314. body.scoreProfiles.generic.should.have.a.property('categories').that.is.an('object');
  315. body.should.have.a.property('rules').that.is.an('object');
  316. body.should.have.a.property('toolsResults').that.is.an('object');
  317. body.toolsResults.should.have.a.property('phantomas').that.is.an('object');
  318. body.should.have.a.property('javascriptExecutionTree').that.is.an('object');
  319. body.javascriptExecutionTree.should.have.a.property('data').that.is.an('object');
  320. body.javascriptExecutionTree.data.should.have.a.property('type').that.equals('main');
  321. done();
  322. } else {
  323. done(error || response.statusCode);
  324. }
  325. });
  326. });
  327. it('should return the generic score object', function(done) {
  328. this.timeout(5000);
  329. request({
  330. method: 'GET',
  331. url: serverUrl + '/api/results/' + asyncRunId + '/generalScores',
  332. json: true,
  333. }, function(error, response, body) {
  334. if (!error && response.statusCode === 200) {
  335. body.should.have.a.property('globalScore').that.is.a('number');
  336. body.should.have.a.property('categories').that.is.an('object');
  337. done();
  338. } else {
  339. done(error || response.statusCode);
  340. }
  341. });
  342. });
  343. it('should return the generic score object also', function(done) {
  344. this.timeout(5000);
  345. request({
  346. method: 'GET',
  347. url: serverUrl + '/api/results/' + asyncRunId + '/generalScores/generic',
  348. json: true,
  349. }, function(error, response, body) {
  350. if (!error && response.statusCode === 200) {
  351. body.should.have.a.property('globalScore').that.is.a('number');
  352. body.should.have.a.property('categories').that.is.an('object');
  353. done();
  354. } else {
  355. done(error || response.statusCode);
  356. }
  357. });
  358. });
  359. it('should not find an unknown score object', function(done) {
  360. this.timeout(5000);
  361. request({
  362. method: 'GET',
  363. url: serverUrl + '/api/results/' + asyncRunId + '/generalScores/unknown',
  364. json: true,
  365. }, function(error, response, body) {
  366. if (!error && response.statusCode === 404) {
  367. done();
  368. } else {
  369. done(error || response.statusCode);
  370. }
  371. });
  372. });
  373. it('should return the rules', function(done) {
  374. this.timeout(5000);
  375. request({
  376. method: 'GET',
  377. url: serverUrl + '/api/results/' + asyncRunId + '/rules',
  378. json: true,
  379. }, function(error, response, body) {
  380. if (!error && response.statusCode === 200) {
  381. var firstRule = body[Object.keys(body)[0]];
  382. firstRule.should.have.a.property('policy').that.is.an('object');
  383. firstRule.should.have.a.property('value').that.is.a('number');
  384. firstRule.should.have.a.property('bad').that.is.a('boolean');
  385. firstRule.should.have.a.property('abnormal').that.is.a('boolean');
  386. firstRule.should.have.a.property('score').that.is.a('number');
  387. firstRule.should.have.a.property('abnormalityScore').that.is.a('number');
  388. done();
  389. } else {
  390. done(error || response.statusCode);
  391. }
  392. });
  393. });
  394. it('should return the javascript execution tree', function(done) {
  395. this.timeout(5000);
  396. request({
  397. method: 'GET',
  398. url: serverUrl + '/api/results/' + asyncRunId + '/javascriptExecutionTree',
  399. json: true,
  400. }, function(error, response, body) {
  401. if (!error && response.statusCode === 200) {
  402. body.should.have.a.property('data').that.is.an('object');
  403. body.data.should.have.a.property('type').that.equals('main');
  404. done();
  405. } else {
  406. done(error || response.statusCode);
  407. }
  408. });
  409. });
  410. it('should return the phantomas results', function(done) {
  411. this.timeout(5000);
  412. request({
  413. method: 'GET',
  414. url: serverUrl + '/api/results/' + asyncRunId + '/toolsResults/phantomas',
  415. json: true,
  416. }, function(error, response, body) {
  417. if (!error && response.statusCode === 200) {
  418. body.should.have.a.property('metrics').that.is.an('object');
  419. body.should.have.a.property('offenders').that.is.an('object');
  420. done();
  421. } else {
  422. done(error || response.statusCode);
  423. }
  424. });
  425. });
  426. it('should retrieve the screenshot', function(done) {
  427. this.timeout(5000);
  428. request({
  429. method: 'GET',
  430. url: serverUrl + screenshotUrl
  431. }, function(error, response, body) {
  432. if (!error && response.statusCode === 200) {
  433. response.headers['content-type'].should.equal('image/jpeg');
  434. done();
  435. } else {
  436. done(error || response.statusCode);
  437. }
  438. });
  439. });
  440. it('should fail on a unexistant screenshot', function(done) {
  441. this.timeout(5000);
  442. request({
  443. method: 'GET',
  444. url: serverUrl + '/api/results/000000/screenshot.jpg'
  445. }, function(error, response, body) {
  446. if (!error && response.statusCode === 404) {
  447. done();
  448. } else {
  449. done(error || response.statusCode);
  450. }
  451. });
  452. });
  453. });