api_spec.js 48 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036
  1. var chakram = require("./../setup.js").chakram;
  2. var expect = chakram.expect;
  3. var itPropagatesToTheApi = require("./../setup.js").itPropagatesToTheApi;
  4. var itShowsUpInPdnsAs = require("./../setup.js").itShowsUpInPdnsAs;
  5. var schemas = require("./../schemas.js");
  6. describe("API", function () {
  7. this.timeout(3000);
  8. before(function () {
  9. chakram.setRequestDefaults({
  10. headers: {
  11. 'Host': 'desec.' + process.env.DESECSTACK_DOMAIN,
  12. },
  13. followRedirect: false,
  14. baseUrl: 'https://www/api/v1',
  15. })
  16. });
  17. it("provides an index page", function () {
  18. var response = chakram.get('/');
  19. return expect(response).to.have.status(200);
  20. });
  21. describe("user registration", function () {
  22. it("returns a user object", function () {
  23. var email, password, token;
  24. email = require("uuid").v4() + '@e2etest.local';
  25. password = require("uuid").v4();
  26. var response = chakram.post('/auth/users/create/', {
  27. "email": email,
  28. "password": password,
  29. });
  30. return expect(response).to.have.status(201);
  31. });
  32. it("locks new users that look suspicious");
  33. });
  34. describe("user login", function () {
  35. var email, password, token;
  36. before(function () {
  37. // register a user that we can work with
  38. email = require("uuid").v4() + '@e2etest.local';
  39. password = require("uuid").v4();
  40. var response = chakram.post('/auth/users/create/', {
  41. "email": email,
  42. "password": password,
  43. });
  44. return expect(response).to.have.status(201);
  45. });
  46. it("returns a token", function () {
  47. return chakram.post('/auth/token/create/', {
  48. "email": email,
  49. "password": password,
  50. }).then(function (loginResponse) {
  51. expect(loginResponse.body.auth_token).to.match(schemas.TOKEN_REGEX);
  52. token = loginResponse.body.auth_token;
  53. });
  54. });
  55. describe("token management (djoser)", function () {
  56. var token1, token2;
  57. function createTwoTokens() {
  58. return chakram.waitFor([
  59. chakram.post('/auth/token/create/', {
  60. "email": email,
  61. "password": password,
  62. }).then(function (loginResponse) {
  63. expect(loginResponse).to.have.status(201);
  64. expect(loginResponse.body.auth_token).to.match(schemas.TOKEN_REGEX);
  65. token1 = loginResponse.body.auth_token;
  66. expect(token1).to.not.equal(token2);
  67. }),
  68. chakram.post('/auth/token/create/', {
  69. "email": email,
  70. "password": password,
  71. }).then(function (loginResponse) {
  72. expect(loginResponse).to.have.status(201);
  73. expect(loginResponse.body.auth_token).to.match(schemas.TOKEN_REGEX);
  74. token2 = loginResponse.body.auth_token;
  75. expect(token2).to.not.equal(token1);
  76. })
  77. ]);
  78. }
  79. function deleteToken(token) {
  80. var response = chakram.post('/auth/token/destroy/', null, {
  81. headers: {'Authorization': 'Token ' + token}
  82. });
  83. return expect(response).to.have.status(204);
  84. }
  85. it("can create additional tokens", createTwoTokens);
  86. describe("additional tokens", function () {
  87. before(createTwoTokens);
  88. it("can be used for login (1)", function () {
  89. return expect(chakram.get('/domains/', {
  90. headers: {'Authorization': 'Token ' + token1 }
  91. })).to.have.status(200);
  92. });
  93. it("can be used for login (2)", function () {
  94. return expect(chakram.get('/domains/', {
  95. headers: {'Authorization': 'Token ' + token2 }
  96. })).to.have.status(200);
  97. });
  98. describe("and one deleted", function () {
  99. before(function () {
  100. var response = chakram.post('/auth/token/destroy/', undefined,
  101. { headers: {'Authorization': 'Token ' + token1 } }
  102. );
  103. return expect(response).to.have.status(204);
  104. });
  105. it("leaves the other untouched", function () {
  106. return expect(chakram.get('/domains/', {
  107. headers: {'Authorization': 'Token ' + token2 }
  108. })).to.have.status(200);
  109. });
  110. });
  111. });
  112. });
  113. });
  114. var email = require("uuid").v4() + '@e2etest.local';
  115. describe("with user account [" + email + "]", function () {
  116. var apiHomeSchema = {
  117. properties: {
  118. domains: {type: "string"},
  119. logout: {type: "string"},
  120. user: {type: "string"},
  121. },
  122. required: ["domains", "logout", "user"]
  123. };
  124. var password, token;
  125. before(function () {
  126. chakram.setRequestSettings({
  127. headers: {
  128. 'Host': 'desec.' + process.env.DESECSTACK_DOMAIN,
  129. },
  130. followRedirect: false,
  131. baseUrl: 'https://www/api/v1',
  132. });
  133. // register a user that we can login and work with
  134. password = require("uuid").v4();
  135. return chakram.post('/auth/users/create/', {
  136. "email": email,
  137. "password": password,
  138. }).then(function () {
  139. return chakram.post('/auth/token/create/', {
  140. "email": email,
  141. "password": password,
  142. }).then(function (loginResponse) {
  143. expect(loginResponse.body.auth_token).to.match(schemas.TOKEN_REGEX);
  144. token = loginResponse.body.auth_token;
  145. chakram.setRequestHeader('Authorization', 'Token ' + token);
  146. });
  147. });
  148. });
  149. describe("(logged in)", function () {
  150. describe("api 'homepage'", function () {
  151. var response;
  152. before(function () {
  153. response = chakram.get('/');
  154. });
  155. it('has status 200', function () {
  156. return expect(response).to.have.status(200);
  157. });
  158. it('looks according to the schema', function () {
  159. return expect(response).to.have.schema(apiHomeSchema);
  160. });
  161. });
  162. describe("on domains/ endpoint", function () {
  163. var domain = 'e2etest-' + require("uuid").v4() + '.dedyn.io';
  164. before(function () {
  165. return expect(chakram.post('/domains/', {'name': domain})).to.have.status(201);
  166. });
  167. it("can register a domain name", function () {
  168. var response = chakram.get('/domains/' + domain + '/');
  169. expect(response).to.have.status(200);
  170. expect(response).to.have.schema(schemas.domain);
  171. return chakram.wait();
  172. });
  173. describe("on rrsets/ endpoint", function () {
  174. it("can retrieve RRsets", function () {
  175. var response = chakram.get('/domains/' + domain + '/rrsets/');
  176. expect(response).to.have.status(200);
  177. expect(response).to.have.schema(schemas.rrsets);
  178. response = chakram.get('/domains/' + domain + '/rrsets/.../NS/');
  179. expect(response).to.have.status(200);
  180. expect(response).to.have.schema(schemas.rrset);
  181. return chakram.wait();
  182. });
  183. });
  184. });
  185. describe('POST rrsets/ with fresh domain', function () {
  186. var domain = 'e2etest-' + require("uuid").v4() + '.dedyn.io';
  187. before(function () {
  188. return expect(chakram.post('/domains/', {'name': domain})).to.have.status(201);
  189. });
  190. describe("can set an A RRset", function () {
  191. before(function () {
  192. var response = chakram.post(
  193. '/domains/' + domain + '/rrsets/',
  194. {'subname': '', 'type': 'A', 'records': ['127.0.0.1'], 'ttl': 60}
  195. );
  196. expect(response).to.have.status(201);
  197. expect(response).to.have.schema(schemas.rrset);
  198. expect(response).to.have.json('ttl', 60);
  199. expect(response).to.have.json('records', ['127.0.0.1']);
  200. return chakram.wait();
  201. });
  202. itPropagatesToTheApi([
  203. {subname: '', domain: domain, type: 'A', ttl: 60, records: ['127.0.0.1']},
  204. ]);
  205. itShowsUpInPdnsAs('', domain, 'A', ['127.0.0.1'], 60);
  206. });
  207. it("cannot update RRSets for nonexistent domain name", function () {
  208. return expect(chakram.patch(
  209. '/domains/nonexistent.e2e.domain/rrsets/',
  210. {'subname': '', 'type': 'A', 'records': ['127.0.0.1'], 'ttl': 60}
  211. )).to.have.status(404);
  212. });
  213. it("cannot create RRSets for nonexistent domain name", function () {
  214. return expect(chakram.post(
  215. '/domains/nonexistent.e2e.domain/rrsets/',
  216. {'subname': '', 'type': 'A', 'records': ['127.0.0.1'], 'ttl': 60}
  217. )).to.have.status(404);
  218. });
  219. describe("can set a wildcard AAAA RRset with multiple records", function () {
  220. before(function () {
  221. return chakram.post(
  222. '/domains/' + domain + '/rrsets/',
  223. {'subname': '*.foobar', 'type': 'AAAA', 'records': ['::1', 'bade::affe'], 'ttl': 60}
  224. );
  225. });
  226. itPropagatesToTheApi([
  227. {subname: '*.foobar', domain: domain, type: 'AAAA', ttl: 60, records: ['::1', 'bade::affe']},
  228. {subname: '*.foobar', domain: domain, type: 'AAAA', records: ['bade::affe', '::1']},
  229. ]);
  230. itShowsUpInPdnsAs('test.foobar', domain, 'AAAA', ['::1', 'bade::affe'], 60);
  231. });
  232. describe("can bulk-post an AAAA and an MX record", function () {
  233. before(function () {
  234. var response = chakram.post(
  235. '/domains/' + domain + '/rrsets/',
  236. [
  237. { 'subname': 'ipv6', 'type': 'AAAA', 'records': ['dead::beef'], 'ttl': 22 },
  238. { /* implied: 'subname': '', */ 'type': 'MX', 'records': ['10 mail.example.com.', '20 mail.example.net.'], 'ttl': 33 }
  239. ]
  240. );
  241. expect(response).to.have.status(201);
  242. expect(response).to.have.schema(schemas.rrsets);
  243. return chakram.wait();
  244. });
  245. itPropagatesToTheApi([
  246. {subname: 'ipv6', domain: domain, type: 'AAAA', ttl: 22, records: ['dead::beef']},
  247. {subname: '', domain: domain, type: 'MX', ttl: 33, records: ['10 mail.example.com.', '20 mail.example.net.']},
  248. ]);
  249. itShowsUpInPdnsAs('ipv6', domain, 'AAAA', ['dead::beef'], 22);
  250. itShowsUpInPdnsAs('', domain, 'MX', ['10 mail.example.com.', '20 mail.example.net.'], 33);
  251. });
  252. describe("cannot bulk-post with missing or invalid fields", function () {
  253. before(function () {
  254. // Set an RRset that we'll try to overwrite
  255. var response = chakram.post(
  256. '/domains/' + domain + '/rrsets/',
  257. [{'ttl': 50, 'type': 'TXT', 'records': ['"foo"']}]
  258. );
  259. expect(response).to.have.status(201);
  260. var response = chakram.post(
  261. '/domains/' + domain + '/rrsets/',
  262. [
  263. {'subname': 'a.1', 'records': ['dead::beef'], 'ttl': 22},
  264. {'subname': 'b.1', 'ttl': -50, 'type': 'AAAA', 'records': ['dead::beef']},
  265. {'ttl': 40, 'type': 'TXT', 'records': ['"bar"']},
  266. {'subname': 'c.1', 'records': ['dead::beef'], 'type': 'AAAA'},
  267. {'subname': 'd.1', 'ttl': 50, 'type': 'AAAA'},
  268. {'subname': 'd.1', 'ttl': 50, 'type': 'SOA', 'records': ['ns1.desec.io. peter.desec.io. 2018034419 10800 3600 604800 60']},
  269. {'subname': 'd.1', 'ttl': 50, 'type': 'OPT', 'records': ['9999']},
  270. {'subname': 'd.1', 'ttl': 50, 'type': 'TYPE099', 'records': ['v=spf1 mx -all']},
  271. ]
  272. );
  273. expect(response).to.have.status(400);
  274. expect(response).to.have.json([
  275. { type: [ 'This field is required.' ] },
  276. { ttl: [ 'Ensure this value is greater than or equal to 1.' ] },
  277. {},
  278. { ttl: [ 'This field is required.' ] },
  279. { records: [ 'This field is required.' ] },
  280. { type: [ 'You cannot tinker with the SOA RRset.' ] },
  281. { type: [ 'You cannot tinker with the OPT RRset.' ] },
  282. { type: [ 'Generic type format is not supported.' ] },
  283. ]);
  284. return chakram.wait();
  285. });
  286. it("does not propagate partially to the API", function () {
  287. return chakram.waitFor([
  288. chakram
  289. .get('/domains/' + domain + '/rrsets/b.1.../AAAA/')
  290. .then(function (response) {
  291. expect(response).to.have.status(404);
  292. }),
  293. chakram
  294. .get('/domains/' + domain + '/rrsets/.../TXT/')
  295. .then(function (response) {
  296. expect(response).to.have.status(200);
  297. expect(response).to.have.json('ttl', 50);
  298. expect(response.body.records).to.have.members(['"foo"']);
  299. }),
  300. ]);
  301. });
  302. itShowsUpInPdnsAs('b.1', domain, 'AAAA', []);
  303. });
  304. context("with a pre-existing RRset", function () {
  305. before(function () {
  306. var response = chakram.post(
  307. '/domains/' + domain + '/rrsets/',
  308. [
  309. {'subname': 'a.2', 'ttl': 50, 'type': 'TXT', 'records': ['"foo"']},
  310. {'subname': 'c.2', 'ttl': 50, 'type': 'TXT', 'records': ['"foo"']},
  311. {'subname': 'delete-test', 'ttl': 50, 'type': 'A', 'records': ['127.1.2.3']},
  312. ]
  313. );
  314. return expect(response).to.have.status(201);
  315. });
  316. describe("can delete an RRset", function () {
  317. before(function () {
  318. var response = chakram.delete('/domains/' + domain + '/rrsets/delete-test.../A/');
  319. return expect(response).to.have.status(204);
  320. });
  321. itPropagatesToTheApi([
  322. {subname: 'delete-test', domain: domain, type: 'A', records: []},
  323. ]);
  324. itShowsUpInPdnsAs('delete-test', domain, 'A', []);
  325. });
  326. describe("cannot bulk-post existing or duplicate RRsets", function () {
  327. var response;
  328. before(function () {
  329. response = chakram.post(
  330. '/domains/' + domain + '/rrsets/',
  331. [
  332. {'subname': 'a.2', 'ttl': 40, 'type': 'TXT', 'records': ['"bar"']},
  333. {'subname': 'a.2', 'ttl': 40, 'type': 'TXT', 'records': ['"bar"']},
  334. ]
  335. );
  336. expect(response).to.have.status(400);
  337. return chakram.wait();
  338. });
  339. it("gives the right response", function () {
  340. expect(response).to.have.json([
  341. { '__all__': [ 'R rset with this Domain, Subname and Type already exists.' ] },
  342. { '__all__': [ 'RRset repeated with same subname and type.' ] },
  343. ]);
  344. return chakram.wait();
  345. });
  346. it("does not touch records in the API", function () {
  347. return chakram
  348. .get('/domains/' + domain + '/rrsets/a.2.../TXT/')
  349. .then(function (response) {
  350. expect(response).to.have.status(200);
  351. expect(response).to.have.json('ttl', 50);
  352. expect(response.body.records).to.have.members(['"foo"']);
  353. });
  354. });
  355. itShowsUpInPdnsAs('a.2', domain, 'TXT', ['"foo"'], 50);
  356. });
  357. describe("cannot delete RRsets via bulk-post", function () {
  358. var response;
  359. before(function () {
  360. response = chakram.post(
  361. '/domains/' + domain + '/rrsets/',
  362. [
  363. {'subname': 'c.2', 'ttl': 40, 'type': 'TXT', 'records': []},
  364. ]
  365. );
  366. return expect(response).to.have.status(400);
  367. });
  368. it("gives the right response", function () {
  369. return expect(response).to.have.json([
  370. { '__all__': [ 'R rset with this Domain, Subname and Type already exists.' ] },
  371. ]);
  372. });
  373. });
  374. });
  375. describe("cannot bulk-post with invalid input", function () {
  376. it("gives the right response for invalid type", function () {
  377. var response = chakram.post(
  378. '/domains/' + domain + '/rrsets/',
  379. [{'subname': 'a.2', 'ttl': 50, 'type': 'INVALID', 'records': ['"foo"']}]
  380. );
  381. return expect(response).to.have.status(422);
  382. });
  383. it("gives the right response for invalid records", function () {
  384. var response = chakram.post(
  385. '/domains/' + domain + '/rrsets/',
  386. [{'subname': 'a.2', 'ttl': 50, 'type': 'MX', 'records': ['1.2.3.4']}]
  387. );
  388. return expect(response).to.have.status(422);
  389. });
  390. });
  391. });
  392. describe('PUT rrsets/ with fresh domain', function () {
  393. var domain = 'e2etest-' + require("uuid").v4() + '.dedyn.io';
  394. before(function () {
  395. return expect(chakram.post('/domains/', {'name': domain})).to.have.status(201);
  396. });
  397. describe("can overwrite a single existing RRset using PUT", function () {
  398. before(function () {
  399. var response = chakram.post(
  400. '/domains/' + domain + '/rrsets/',
  401. { 'subname': 'single', 'type': 'AAAA', 'records': ['bade::fefe'], 'ttl': 62 }
  402. ).then(function () {
  403. return chakram.put(
  404. '/domains/' + domain + '/rrsets/single.../AAAA/',
  405. { 'records': ['fefe::bade'], 'ttl': 31 }
  406. );
  407. });
  408. expect(response).to.have.status(200);
  409. expect(response).to.have.schema(schemas.rrset);
  410. return chakram.wait();
  411. });
  412. itPropagatesToTheApi([
  413. {subname: 'single', domain: domain, type: 'AAAA', ttl: 31, records: ['fefe::bade']},
  414. ]);
  415. itShowsUpInPdnsAs('single', domain, 'AAAA', ['fefe::bade'], 31);
  416. });
  417. describe("can bulk-put an AAAA and an MX record", function () {
  418. before(function () {
  419. var response = chakram.put(
  420. '/domains/' + domain + '/rrsets/',
  421. [
  422. { 'subname': 'ipv6', 'type': 'AAAA', 'records': ['dead::beef'], 'ttl': 22 },
  423. { /* implied: 'subname': '', */ 'type': 'MX', 'records': ['10 mail.example.com.', '20 mail.example.net.'], 'ttl': 33 }
  424. ]
  425. );
  426. expect(response).to.have.status(200);
  427. expect(response).to.have.schema(schemas.rrsets);
  428. return chakram.wait();
  429. });
  430. itPropagatesToTheApi([
  431. {subname: 'ipv6', domain: domain, type: 'AAAA', ttl: 22, records: ['dead::beef']},
  432. {subname: '', domain: domain, type: 'MX', ttl: 33, records: ['10 mail.example.com.', '20 mail.example.net.']},
  433. ]);
  434. itShowsUpInPdnsAs('ipv6', domain, 'AAAA', ['dead::beef'], 22);
  435. itShowsUpInPdnsAs('', domain, 'MX', ['10 mail.example.com.', '20 mail.example.net.'], 33);
  436. });
  437. describe("cannot bulk-put with missing or invalid fields", function () {
  438. before(function () {
  439. // Set an RRset that we'll try to overwrite
  440. var response = chakram.put(
  441. '/domains/' + domain + '/rrsets/',
  442. [{'ttl': 50, 'type': 'TXT', 'records': ['"foo"']}]
  443. );
  444. expect(response).to.have.status(200);
  445. var response = chakram.put(
  446. '/domains/' + domain + '/rrsets/',
  447. [
  448. {'subname': 'a.1', 'records': ['dead::beef'], 'ttl': 22},
  449. {'subname': 'b.1', 'ttl': -50, 'type': 'AAAA', 'records': ['dead::beef']},
  450. {'ttl': 40, 'type': 'TXT', 'records': ['"bar"']},
  451. {'subname': 'c.1', 'records': ['dead::beef'], 'type': 'AAAA'},
  452. {'subname': 'd.1', 'ttl': 50, 'type': 'AAAA'},
  453. ]
  454. );
  455. expect(response).to.have.status(400);
  456. expect(response).to.have.json([
  457. { type: [ 'This field is required.' ] },
  458. { ttl: [ 'Ensure this value is greater than or equal to 1.' ] },
  459. {},
  460. { ttl: [ 'This field is required.' ] },
  461. { records: [ 'This field is required.' ] },
  462. ]);
  463. return chakram.wait();
  464. });
  465. it("does not propagate partially to the API", function () {
  466. return chakram.waitFor([
  467. chakram
  468. .get('/domains/' + domain + '/rrsets/b.1.../AAAA/')
  469. .then(function (response) {
  470. expect(response).to.have.status(404);
  471. }),
  472. chakram
  473. .get('/domains/' + domain + '/rrsets/.../TXT/')
  474. .then(function (response) {
  475. expect(response).to.have.status(200);
  476. expect(response).to.have.json('ttl', 50);
  477. expect(response.body.records).to.have.members(['"foo"']);
  478. }),
  479. ]);
  480. });
  481. itShowsUpInPdnsAs('b.1', domain, 'AAAA', []);
  482. });
  483. context("with a pre-existing RRset", function () {
  484. before(function () {
  485. var response = chakram.post(
  486. '/domains/' + domain + '/rrsets/',
  487. [
  488. {'subname': 'a.2', 'ttl': 50, 'type': 'TXT', 'records': ['"foo"']},
  489. {'subname': 'b.2', 'ttl': 50, 'type': 'TXT', 'records': ['"foo"']},
  490. {'subname': 'c.2', 'ttl': 50, 'type': 'A', 'records': ['1.2.3.4']},
  491. ]
  492. );
  493. expect(response).to.have.status(201);
  494. return chakram.wait();
  495. });
  496. describe("can bulk-put existing RRsets", function () {
  497. var response;
  498. before(function () {
  499. response = chakram.put(
  500. '/domains/' + domain + '/rrsets/',
  501. [
  502. {'subname': 'a.2', 'ttl': 40, 'type': 'TXT', 'records': ['"bar"']},
  503. ]
  504. );
  505. expect(response).to.have.status(200);
  506. expect(response).to.have.schema(schemas.rrsets);
  507. return chakram.wait();
  508. });
  509. it("does modify records in the API", function () {
  510. return chakram
  511. .get('/domains/' + domain + '/rrsets/a.2.../TXT/')
  512. .then(function (response) {
  513. expect(response).to.have.status(200);
  514. expect(response).to.have.json('ttl', 40);
  515. expect(response.body.records).to.have.members(['"bar"']);
  516. });
  517. });
  518. itShowsUpInPdnsAs('a.2', domain, 'TXT', ['"bar"'], 40);
  519. });
  520. describe("cannot bulk-put duplicate RRsets", function () {
  521. var response;
  522. before(function () {
  523. response = chakram.put(
  524. '/domains/' + domain + '/rrsets/',
  525. [
  526. {'subname': 'b.2', 'ttl': 60, 'type': 'TXT', 'records': ['"bar"']},
  527. {'subname': 'b.2', 'ttl': 60, 'type': 'TXT', 'records': ['"bar"']},
  528. ]
  529. );
  530. return expect(response).to.have.status(400);
  531. });
  532. it("gives the right response", function () {
  533. return expect(response).to.have.json([
  534. { },
  535. { '__all__': [ 'RRset repeated with same subname and type.' ] },
  536. ]);
  537. });
  538. it("does not touch records in the API", function () {
  539. return chakram
  540. .get('/domains/' + domain + '/rrsets/b.2.../TXT/')
  541. .then(function (response) {
  542. expect(response).to.have.status(200);
  543. expect(response).to.have.json('ttl', 50);
  544. expect(response.body.records).to.have.members(['"foo"']);
  545. });
  546. });
  547. itShowsUpInPdnsAs('b.2', domain, 'TXT', ['"foo"'], 50);
  548. });
  549. describe("can delete RRsets via bulk-put", function () {
  550. var response;
  551. before(function () {
  552. response = chakram.put(
  553. '/domains/' + domain + '/rrsets/',
  554. [
  555. {'subname': 'c.2', 'ttl': 40, 'type': 'A', 'records': []},
  556. ]
  557. );
  558. return expect(response).to.have.status(200);
  559. });
  560. it("gives the right response", function () {
  561. var response = chakram.get('/domains/' + domain + '/rrsets/c.2.../A/');
  562. return expect(response).to.have.status(404);
  563. });
  564. });
  565. });
  566. describe("cannot bulk-put with invalid input", function () {
  567. it("gives the right response for invalid type", function () {
  568. var response = chakram.put(
  569. '/domains/' + domain + '/rrsets/',
  570. [{'subname': 'a.2', 'ttl': 50, 'type': 'INVALID', 'records': ['"foo"']}]
  571. );
  572. return expect(response).to.have.status(422);
  573. });
  574. it("gives the right response for invalid records", function () {
  575. var response = chakram.put(
  576. '/domains/' + domain + '/rrsets/',
  577. [{'subname': 'a.2', 'ttl': 50, 'type': 'MX', 'records': ['1.2.3.4']}]
  578. );
  579. return expect(response).to.have.status(422);
  580. });
  581. });
  582. });
  583. describe('PATCH rrsets/ with fresh domain', function () {
  584. var domain = 'e2etest-' + require("uuid").v4() + '.dedyn.io';
  585. before(function () {
  586. return expect(chakram.post('/domains/', {'name': domain})).to.have.status(201);
  587. });
  588. describe("can modify a single existing RRset using PATCH", function () {
  589. before(function () {
  590. var response = chakram.post(
  591. '/domains/' + domain + '/rrsets/',
  592. { 'subname': 'single', 'type': 'AAAA', 'records': ['bade::fefe'], 'ttl': 62 }
  593. ).then(function () {
  594. return chakram.patch(
  595. '/domains/' + domain + '/rrsets/single.../AAAA/',
  596. { 'records': ['fefe::bade'], 'ttl': 31 }
  597. );
  598. });
  599. expect(response).to.have.status(200);
  600. expect(response).to.have.schema(schemas.rrset);
  601. return chakram.wait();
  602. });
  603. itPropagatesToTheApi([
  604. {subname: 'single', domain: domain, type: 'AAAA', ttl: 31, records: ['fefe::bade']},
  605. ]);
  606. itShowsUpInPdnsAs('single', domain, 'AAAA', ['fefe::bade'], 31);
  607. });
  608. describe("can bulk-patch an AAAA and an MX record", function () {
  609. before(function () {
  610. var response = chakram.patch(
  611. '/domains/' + domain + '/rrsets/',
  612. [
  613. { 'subname': 'ipv6', 'type': 'AAAA', 'records': ['dead::beef'], 'ttl': 22 },
  614. { /* implied: 'subname': '', */ 'type': 'MX', 'records': ['10 mail.example.com.', '20 mail.example.net.'], 'ttl': 33 }
  615. ]
  616. );
  617. expect(response).to.have.status(200);
  618. expect(response).to.have.schema(schemas.rrsets);
  619. return chakram.wait();
  620. });
  621. itPropagatesToTheApi([
  622. {subname: 'ipv6', domain: domain, type: 'AAAA', ttl: 22, records: ['dead::beef']},
  623. {subname: '', domain: domain, type: 'MX', ttl: 33, records: ['10 mail.example.com.', '20 mail.example.net.']},
  624. ]);
  625. itShowsUpInPdnsAs('ipv6', domain, 'AAAA', ['dead::beef'], 22);
  626. itShowsUpInPdnsAs('', domain, 'MX', ['10 mail.example.com.', '20 mail.example.net.'], 33);
  627. });
  628. describe("cannot bulk-patch with missing or invalid fields", function () {
  629. before(function () {
  630. // Set an RRset that we'll try to overwrite
  631. var response = chakram.post(
  632. '/domains/' + domain + '/rrsets/',
  633. [{'ttl': 50, 'type': 'TXT', 'records': ['"foo"']}]
  634. );
  635. expect(response).to.have.status(201);
  636. var response = chakram.patch(
  637. '/domains/' + domain + '/rrsets/',
  638. [
  639. {'subname': 'a.1', 'records': ['dead::beef'], 'ttl': 22},
  640. {'subname': 'b.1', 'ttl': -50, 'type': 'AAAA', 'records': ['dead::beef']},
  641. {'ttl': 40, 'type': 'TXT', 'records': ['"bar"']},
  642. {'subname': 'c.1', 'records': ['dead::beef'], 'type': 'AAAA'},
  643. {'subname': 'd.1', 'ttl': 50, 'type': 'AAAA'},
  644. ]
  645. );
  646. expect(response).to.have.status(400);
  647. expect(response).to.have.json([
  648. { type: [ 'This field is required.' ] },
  649. { ttl: [ 'Ensure this value is greater than or equal to 1.' ] },
  650. {},
  651. {},
  652. {},
  653. ]);
  654. return chakram.wait();
  655. });
  656. it("does not propagate partially to the API", function () {
  657. return chakram.waitFor([
  658. chakram
  659. .get('/domains/' + domain + '/rrsets/b.1.../AAAA/')
  660. .then(function (response) {
  661. expect(response).to.have.status(404);
  662. }),
  663. chakram
  664. .get('/domains/' + domain + '/rrsets/.../TXT/')
  665. .then(function (response) {
  666. expect(response).to.have.status(200);
  667. expect(response).to.have.json('ttl', 50);
  668. expect(response.body.records).to.have.members(['"foo"']);
  669. }),
  670. ]);
  671. });
  672. itShowsUpInPdnsAs('b.1', domain, 'AAAA', []);
  673. });
  674. context("with a pre-existing RRset", function () {
  675. before(function () {
  676. var response = chakram.post(
  677. '/domains/' + domain + '/rrsets/',
  678. [
  679. {'subname': 'a.1', 'ttl': 50, 'type': 'TXT', 'records': ['"foo"']},
  680. {'subname': 'a.2', 'ttl': 50, 'type': 'A', 'records': ['4.3.2.1']},
  681. {'subname': 'a.2', 'ttl': 50, 'type': 'TXT', 'records': ['"foo"']},
  682. {'subname': 'b.2', 'ttl': 50, 'type': 'A', 'records': ['5.4.3.2']},
  683. {'subname': 'b.2', 'ttl': 50, 'type': 'TXT', 'records': ['"foo"']},
  684. {'subname': 'c.2', 'ttl': 50, 'type': 'A', 'records': ['1.2.3.4']},
  685. ]
  686. );
  687. return expect(response).to.have.status(201);
  688. });
  689. describe("can bulk-patch existing RRsets", function () {
  690. var response;
  691. before(function () {
  692. response = chakram.patch(
  693. '/domains/' + domain + '/rrsets/',
  694. [
  695. {'subname': 'a.1', 'type': 'TXT', 'records': ['"bar"']},
  696. {'subname': 'a.2', 'ttl': 40, 'type': 'TXT', 'records': ['"bar"']},
  697. ]
  698. );
  699. expect(response).to.have.status(200);
  700. expect(response).to.have.schema(schemas.rrsets);
  701. return chakram.wait();
  702. });
  703. it("does modify records in the API", function () {
  704. return chakram.waitFor([
  705. chakram
  706. .get('/domains/' + domain + '/rrsets/a.1.../TXT/')
  707. .then(function (response) {
  708. expect(response).to.have.status(200);
  709. expect(response).to.have.json('ttl', 50);
  710. expect(response.body.records).to.have.members(['"bar"']);
  711. }),
  712. chakram
  713. .get('/domains/' + domain + '/rrsets/a.2.../TXT/')
  714. .then(function (response) {
  715. expect(response).to.have.status(200);
  716. expect(response).to.have.json('ttl', 40);
  717. expect(response.body.records).to.have.members(['"bar"']);
  718. }),
  719. ]);
  720. });
  721. itShowsUpInPdnsAs('a.2', domain, 'TXT', ['"bar"'], 40);
  722. });
  723. describe("cannot bulk-patch duplicate RRsets", function () {
  724. var response;
  725. before(function () {
  726. response = chakram.patch(
  727. '/domains/' + domain + '/rrsets/',
  728. [
  729. {'subname': 'b.2', 'ttl': 40, 'type': 'TXT', 'records': ['"bar"']},
  730. {'subname': 'b.2', 'ttl': 40, 'type': 'TXT', 'records': ['"bar"']},
  731. ]
  732. );
  733. return expect(response).to.have.status(400);
  734. });
  735. it("gives the right response", function () {
  736. return expect(response).to.have.json([
  737. {},
  738. { '__all__': [ 'RRset repeated with same subname and type.' ] },
  739. ]);
  740. });
  741. it("does not touch records in the API", function () {
  742. return chakram
  743. .get('/domains/' + domain + '/rrsets/b.2.../TXT/')
  744. .then(function (response) {
  745. expect(response).to.have.status(200);
  746. expect(response).to.have.json('ttl', 50);
  747. expect(response.body.records).to.have.members(['"foo"']);
  748. });
  749. });
  750. itShowsUpInPdnsAs('b.2', domain, 'TXT', ['"foo"'], 50);
  751. });
  752. describe("can delete RRsets via bulk-patch", function () {
  753. var response;
  754. before(function () {
  755. response = chakram.patch(
  756. '/domains/' + domain + '/rrsets/',
  757. [
  758. {'subname': 'c.2', 'type': 'A', 'records': []},
  759. ]
  760. );
  761. return expect(response).to.have.status(200);
  762. });
  763. it("gives the right response", function () {
  764. var response = chakram.get('/domains/' + domain + '/rrsets/c.2.../A/');
  765. return expect(response).to.have.status(404);
  766. });
  767. });
  768. describe("accepts missing fields for no-op requests via bulk-patch", function () {
  769. var response;
  770. before(function () {
  771. response = chakram.patch(
  772. '/domains/' + domain + '/rrsets/',
  773. [
  774. {'subname': 'a.2', 'type': 'A', 'records': ['6.6.6.6']}, // existing RRset; TTL not needed
  775. {'subname': 'b.2', 'type': 'A', 'ttl': 40}, // existing RRset; records not needed
  776. {'subname': 'x.2', 'type': 'A', 'records': []}, // non-existent, no-op
  777. {'subname': 'x.2', 'type': 'AAAA'}, // non-existent, no-op
  778. {'subname': 'x.2', 'type': 'TXT', 'ttl': 32}, // non-existent, no-op
  779. ]
  780. );
  781. return expect(response).to.have.status(200);
  782. });
  783. it("gives the right response", function () {
  784. var response = chakram.get('/domains/' + domain + '/rrsets/b.2.../A/');
  785. expect(response).to.have.status(200);
  786. expect(response).to.have.json('ttl', 40);
  787. return chakram.wait();
  788. });
  789. });
  790. describe("catches invalid type for no-op request via bulk-patch", function () {
  791. it("gives the right response", function () {
  792. return chakram.patch(
  793. '/domains/' + domain + '/rrsets/',
  794. [
  795. {'subname': 'x.2', 'type': 'AAA'}, // non-existent, no-op, but invalid type
  796. ]
  797. ).then(function (respObj) {
  798. expect(respObj).to.have.status(422);
  799. expect(respObj.body.detail).to.match(/IN AAA: unknown type given$/);
  800. return chakram.wait();
  801. });
  802. });
  803. });
  804. });
  805. describe("cannot bulk-patch with invalid input", function () {
  806. it("gives the right response for invalid type", function () {
  807. var response = chakram.patch(
  808. '/domains/' + domain + '/rrsets/',
  809. [{'subname': 'a.2', 'ttl': 50, 'type': 'INVALID', 'records': ['"foo"']}]
  810. );
  811. return expect(response).to.have.status(422);
  812. });
  813. it("gives the right response for invalid records", function () {
  814. var response = chakram.patch(
  815. '/domains/' + domain + '/rrsets/',
  816. [{'subname': 'a.2', 'ttl': 50, 'type': 'MX', 'records': ['1.2.3.4']}]
  817. );
  818. return expect(response).to.have.status(422);
  819. });
  820. });
  821. });
  822. describe("tokens/ endpoint", function () {
  823. var tokenId;
  824. var tokenValue;
  825. function createTokenWithName () {
  826. var tokenname = "e2e-token-" + require("uuid").v4();
  827. return chakram.post('/tokens/', { name: tokenname }).then(function (response) {
  828. expect(response).to.have.status(201);
  829. expect(response).to.have.json('name', tokenname);
  830. tokenId = response.body['id'];
  831. });
  832. }
  833. function createToken () {
  834. return chakram.post('/tokens/').then(function (response) {
  835. expect(response).to.have.status(201);
  836. tokenId = response.body['id'];
  837. tokenValue = response.body['value'];
  838. });
  839. }
  840. it("can create tokens", createToken);
  841. it("can create tokens with name", createTokenWithName)
  842. describe("with tokens", function () {
  843. before(createToken)
  844. it("a list of tokens can be retrieved", function () {
  845. var response = chakram.get('/tokens/');
  846. return expect(response).to.have.schema(schemas.tokens);
  847. });
  848. describe("can delete token", function () {
  849. before( function () {
  850. var response = chakram.delete('/tokens/' + tokenId + '/');
  851. return expect(response).to.have.status(204);
  852. });
  853. it("deactivates the token", function () {
  854. return expect(chakram.get('/tokens/', {
  855. headers: {'Authorization': 'Token ' + tokenValue }
  856. })).to.have.status(401);
  857. });
  858. });
  859. it("deleting nonexistent tokens yields 204", function () {
  860. var response = chakram.delete('/tokens/wedonthavethisid/');
  861. return expect(response).to.have.status(204);
  862. });
  863. });
  864. })
  865. });
  866. });
  867. });