apiTest.js 16 KB

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