apiTest.js 17 KB

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