api_spec.js 49 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058
  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. describe("cannot create RRsets of restricted or dead type", function () {
  208. var rrTypes = ['DNAME', 'ALIAS', 'SOA', 'RRSIG', 'DNSKEY', 'NSEC3PARAM', 'OPT'];
  209. for (var i = 0; i < rrTypes.length; i++) {
  210. var rrType = rrTypes[i];
  211. it(rrType, function () {
  212. return expect(chakram.post(
  213. '/domains/' + domain + '/rrsets/',
  214. {'subname': 'not-welcome', 'type': rrType, 'records': ['127.0.0.1'], 'ttl': 60}
  215. )).to.have.status(400);
  216. });
  217. }
  218. });
  219. it("cannot update RRSets for nonexistent domain name", function () {
  220. return expect(chakram.patch(
  221. '/domains/nonexistent.e2e.domain/rrsets/',
  222. {'subname': '', 'type': 'A', 'records': ['127.0.0.1'], 'ttl': 60}
  223. )).to.have.status(404);
  224. });
  225. it("cannot create RRSets for nonexistent domain name", function () {
  226. return expect(chakram.post(
  227. '/domains/nonexistent.e2e.domain/rrsets/',
  228. {'subname': '', 'type': 'A', 'records': ['127.0.0.1'], 'ttl': 60}
  229. )).to.have.status(404);
  230. });
  231. it("cannot set unicode RRsets", function () {
  232. return expect(chakram.post(
  233. '/domains/' + domain + '/rrsets/',
  234. {'subname': '想不出来', 'type': 'A', 'records': ['127.0.0.1'], 'ttl': 60}
  235. )).to.have.status(422);
  236. });
  237. describe("can set a wildcard AAAA RRset with multiple records", function () {
  238. before(function () {
  239. return chakram.post(
  240. '/domains/' + domain + '/rrsets/',
  241. {'subname': '*.foobar', 'type': 'AAAA', 'records': ['::1', 'bade::affe'], 'ttl': 60}
  242. );
  243. });
  244. itPropagatesToTheApi([
  245. {subname: '*.foobar', domain: domain, type: 'AAAA', ttl: 60, records: ['::1', 'bade::affe']},
  246. {subname: '*.foobar', domain: domain, type: 'AAAA', records: ['bade::affe', '::1']},
  247. ]);
  248. itShowsUpInPdnsAs('test.foobar', domain, 'AAAA', ['::1', 'bade::affe'], 60);
  249. });
  250. describe("can bulk-post an AAAA and an MX record", function () {
  251. before(function () {
  252. var response = chakram.post(
  253. '/domains/' + domain + '/rrsets/',
  254. [
  255. { 'subname': 'ipv6', 'type': 'AAAA', 'records': ['dead::beef'], 'ttl': 22 },
  256. { /* implied: 'subname': '', */ 'type': 'MX', 'records': ['10 mail.example.com.', '20 mail.example.net.'], 'ttl': 33 }
  257. ]
  258. );
  259. expect(response).to.have.status(201);
  260. expect(response).to.have.schema(schemas.rrsets);
  261. return chakram.wait();
  262. });
  263. itPropagatesToTheApi([
  264. {subname: 'ipv6', domain: domain, type: 'AAAA', ttl: 22, records: ['dead::beef']},
  265. {subname: '', domain: domain, type: 'MX', ttl: 33, records: ['10 mail.example.com.', '20 mail.example.net.']},
  266. ]);
  267. itShowsUpInPdnsAs('ipv6', domain, 'AAAA', ['dead::beef'], 22);
  268. itShowsUpInPdnsAs('', domain, 'MX', ['10 mail.example.com.', '20 mail.example.net.'], 33);
  269. });
  270. describe("cannot bulk-post with missing or invalid fields", function () {
  271. before(function () {
  272. // Set an RRset that we'll try to overwrite
  273. var response = chakram.post(
  274. '/domains/' + domain + '/rrsets/',
  275. [{'ttl': 50, 'type': 'TXT', 'records': ['"foo"']}]
  276. );
  277. expect(response).to.have.status(201);
  278. var response = chakram.post(
  279. '/domains/' + domain + '/rrsets/',
  280. [
  281. {'subname': 'a.1', 'records': ['dead::beef'], 'ttl': 22},
  282. {'subname': 'b.1', 'ttl': -50, 'type': 'AAAA', 'records': ['dead::beef']},
  283. {'ttl': 40, 'type': 'TXT', 'records': ['"bar"']},
  284. {'subname': 'c.1', 'records': ['dead::beef'], 'type': 'AAAA'},
  285. {'subname': 'd.1', 'ttl': 50, 'type': 'AAAA'},
  286. {'subname': 'd.1', 'ttl': 50, 'type': 'SOA', 'records': ['ns1.desec.io. peter.desec.io. 2018034419 10800 3600 604800 60']},
  287. {'subname': 'd.1', 'ttl': 50, 'type': 'OPT', 'records': ['9999']},
  288. {'subname': 'd.1', 'ttl': 50, 'type': 'TYPE099', 'records': ['v=spf1 mx -all']},
  289. ]
  290. );
  291. expect(response).to.have.status(400);
  292. expect(response).to.have.json([
  293. { type: [ 'This field is required.' ] },
  294. { ttl: [ 'Ensure this value is greater than or equal to 1.' ] },
  295. {},
  296. { ttl: [ 'This field is required.' ] },
  297. { records: [ 'This field is required.' ] },
  298. { type: [ 'You cannot tinker with the SOA RRset.' ] },
  299. { type: [ 'You cannot tinker with the OPT RRset.' ] },
  300. { type: [ 'Generic type format is not supported.' ] },
  301. ]);
  302. return chakram.wait();
  303. });
  304. it("does not propagate partially to the API", function () {
  305. return chakram.waitFor([
  306. chakram
  307. .get('/domains/' + domain + '/rrsets/b.1.../AAAA/')
  308. .then(function (response) {
  309. expect(response).to.have.status(404);
  310. }),
  311. chakram
  312. .get('/domains/' + domain + '/rrsets/.../TXT/')
  313. .then(function (response) {
  314. expect(response).to.have.status(200);
  315. expect(response).to.have.json('ttl', 50);
  316. expect(response.body.records).to.have.members(['"foo"']);
  317. }),
  318. ]);
  319. });
  320. itShowsUpInPdnsAs('b.1', domain, 'AAAA', []);
  321. });
  322. context("with a pre-existing RRset", function () {
  323. before(function () {
  324. var response = chakram.post(
  325. '/domains/' + domain + '/rrsets/',
  326. [
  327. {'subname': 'a.2', 'ttl': 50, 'type': 'TXT', 'records': ['"foo"']},
  328. {'subname': 'c.2', 'ttl': 50, 'type': 'TXT', 'records': ['"foo"']},
  329. {'subname': 'delete-test', 'ttl': 50, 'type': 'A', 'records': ['127.1.2.3']},
  330. ]
  331. );
  332. return expect(response).to.have.status(201);
  333. });
  334. describe("can delete an RRset", function () {
  335. before(function () {
  336. var response = chakram.delete('/domains/' + domain + '/rrsets/delete-test.../A/');
  337. return expect(response).to.have.status(204);
  338. });
  339. itPropagatesToTheApi([
  340. {subname: 'delete-test', domain: domain, type: 'A', records: []},
  341. ]);
  342. itShowsUpInPdnsAs('delete-test', domain, 'A', []);
  343. });
  344. describe("cannot bulk-post existing or duplicate RRsets", function () {
  345. var response;
  346. before(function () {
  347. response = chakram.post(
  348. '/domains/' + domain + '/rrsets/',
  349. [
  350. {'subname': 'a.2', 'ttl': 40, 'type': 'TXT', 'records': ['"bar"']},
  351. {'subname': 'a.2', 'ttl': 40, 'type': 'TXT', 'records': ['"bar"']},
  352. ]
  353. );
  354. expect(response).to.have.status(400);
  355. return chakram.wait();
  356. });
  357. it("gives the right response", function () {
  358. expect(response).to.have.json([
  359. { '__all__': [ 'R rset with this Domain, Subname and Type already exists.' ] },
  360. { '__all__': [ 'RRset repeated with same subname and type.' ] },
  361. ]);
  362. return chakram.wait();
  363. });
  364. it("does not touch records in the API", function () {
  365. return chakram
  366. .get('/domains/' + domain + '/rrsets/a.2.../TXT/')
  367. .then(function (response) {
  368. expect(response).to.have.status(200);
  369. expect(response).to.have.json('ttl', 50);
  370. expect(response.body.records).to.have.members(['"foo"']);
  371. });
  372. });
  373. itShowsUpInPdnsAs('a.2', domain, 'TXT', ['"foo"'], 50);
  374. });
  375. describe("cannot delete RRsets via bulk-post", function () {
  376. var response;
  377. before(function () {
  378. response = chakram.post(
  379. '/domains/' + domain + '/rrsets/',
  380. [
  381. {'subname': 'c.2', 'ttl': 40, 'type': 'TXT', 'records': []},
  382. ]
  383. );
  384. return expect(response).to.have.status(400);
  385. });
  386. it("gives the right response", function () {
  387. return expect(response).to.have.json([
  388. { '__all__': [ 'R rset with this Domain, Subname and Type already exists.' ] },
  389. ]);
  390. });
  391. });
  392. });
  393. describe("cannot bulk-post with invalid input", function () {
  394. it("gives the right response for invalid type", function () {
  395. var response = chakram.post(
  396. '/domains/' + domain + '/rrsets/',
  397. [{'subname': 'a.2', 'ttl': 50, 'type': 'INVALID', 'records': ['"foo"']}]
  398. );
  399. return expect(response).to.have.status(422);
  400. });
  401. it("gives the right response for invalid records", function () {
  402. var response = chakram.post(
  403. '/domains/' + domain + '/rrsets/',
  404. [{'subname': 'a.2', 'ttl': 50, 'type': 'MX', 'records': ['1.2.3.4']}]
  405. );
  406. return expect(response).to.have.status(422);
  407. });
  408. });
  409. });
  410. describe('PUT rrsets/ with fresh domain', function () {
  411. var domain = 'e2etest-' + require("uuid").v4() + '.dedyn.io';
  412. before(function () {
  413. return expect(chakram.post('/domains/', {'name': domain})).to.have.status(201);
  414. });
  415. describe("can overwrite a single existing RRset using PUT", function () {
  416. before(function () {
  417. var response = chakram.post(
  418. '/domains/' + domain + '/rrsets/',
  419. { 'subname': 'single', 'type': 'AAAA', 'records': ['bade::fefe'], 'ttl': 62 }
  420. ).then(function () {
  421. return chakram.put(
  422. '/domains/' + domain + '/rrsets/single.../AAAA/',
  423. { 'records': ['fefe::bade'], 'ttl': 31 }
  424. );
  425. });
  426. expect(response).to.have.status(200);
  427. expect(response).to.have.schema(schemas.rrset);
  428. return chakram.wait();
  429. });
  430. itPropagatesToTheApi([
  431. {subname: 'single', domain: domain, type: 'AAAA', ttl: 31, records: ['fefe::bade']},
  432. ]);
  433. itShowsUpInPdnsAs('single', domain, 'AAAA', ['fefe::bade'], 31);
  434. });
  435. describe("can bulk-put an AAAA and an MX record", function () {
  436. before(function () {
  437. var response = chakram.put(
  438. '/domains/' + domain + '/rrsets/',
  439. [
  440. { 'subname': 'ipv6', 'type': 'AAAA', 'records': ['dead::beef'], 'ttl': 22 },
  441. { /* implied: 'subname': '', */ 'type': 'MX', 'records': ['10 mail.example.com.', '20 mail.example.net.'], 'ttl': 33 }
  442. ]
  443. );
  444. expect(response).to.have.status(200);
  445. expect(response).to.have.schema(schemas.rrsets);
  446. return chakram.wait();
  447. });
  448. itPropagatesToTheApi([
  449. {subname: 'ipv6', domain: domain, type: 'AAAA', ttl: 22, records: ['dead::beef']},
  450. {subname: '', domain: domain, type: 'MX', ttl: 33, records: ['10 mail.example.com.', '20 mail.example.net.']},
  451. ]);
  452. itShowsUpInPdnsAs('ipv6', domain, 'AAAA', ['dead::beef'], 22);
  453. itShowsUpInPdnsAs('', domain, 'MX', ['10 mail.example.com.', '20 mail.example.net.'], 33);
  454. });
  455. describe("cannot bulk-put with missing or invalid fields", function () {
  456. before(function () {
  457. // Set an RRset that we'll try to overwrite
  458. var response = chakram.put(
  459. '/domains/' + domain + '/rrsets/',
  460. [{'ttl': 50, 'type': 'TXT', 'records': ['"foo"']}]
  461. );
  462. expect(response).to.have.status(200);
  463. var response = chakram.put(
  464. '/domains/' + domain + '/rrsets/',
  465. [
  466. {'subname': 'a.1', 'records': ['dead::beef'], 'ttl': 22},
  467. {'subname': 'b.1', 'ttl': -50, 'type': 'AAAA', 'records': ['dead::beef']},
  468. {'ttl': 40, 'type': 'TXT', 'records': ['"bar"']},
  469. {'subname': 'c.1', 'records': ['dead::beef'], 'type': 'AAAA'},
  470. {'subname': 'd.1', 'ttl': 50, 'type': 'AAAA'},
  471. ]
  472. );
  473. expect(response).to.have.status(400);
  474. expect(response).to.have.json([
  475. { type: [ 'This field is required.' ] },
  476. { ttl: [ 'Ensure this value is greater than or equal to 1.' ] },
  477. {},
  478. { ttl: [ 'This field is required.' ] },
  479. { records: [ 'This field is required.' ] },
  480. ]);
  481. return chakram.wait();
  482. });
  483. it("does not propagate partially to the API", function () {
  484. return chakram.waitFor([
  485. chakram
  486. .get('/domains/' + domain + '/rrsets/b.1.../AAAA/')
  487. .then(function (response) {
  488. expect(response).to.have.status(404);
  489. }),
  490. chakram
  491. .get('/domains/' + domain + '/rrsets/.../TXT/')
  492. .then(function (response) {
  493. expect(response).to.have.status(200);
  494. expect(response).to.have.json('ttl', 50);
  495. expect(response.body.records).to.have.members(['"foo"']);
  496. }),
  497. ]);
  498. });
  499. itShowsUpInPdnsAs('b.1', domain, 'AAAA', []);
  500. });
  501. context("with a pre-existing RRset", function () {
  502. before(function () {
  503. var response = chakram.post(
  504. '/domains/' + domain + '/rrsets/',
  505. [
  506. {'subname': 'a.2', 'ttl': 50, 'type': 'TXT', 'records': ['"foo"']},
  507. {'subname': 'b.2', 'ttl': 50, 'type': 'TXT', 'records': ['"foo"']},
  508. {'subname': 'c.2', 'ttl': 50, 'type': 'A', 'records': ['1.2.3.4']},
  509. ]
  510. );
  511. expect(response).to.have.status(201);
  512. return chakram.wait();
  513. });
  514. describe("can bulk-put existing RRsets", function () {
  515. var response;
  516. before(function () {
  517. response = chakram.put(
  518. '/domains/' + domain + '/rrsets/',
  519. [
  520. {'subname': 'a.2', 'ttl': 40, 'type': 'TXT', 'records': ['"bar"']},
  521. ]
  522. );
  523. expect(response).to.have.status(200);
  524. expect(response).to.have.schema(schemas.rrsets);
  525. return chakram.wait();
  526. });
  527. it("does modify records in the API", function () {
  528. return chakram
  529. .get('/domains/' + domain + '/rrsets/a.2.../TXT/')
  530. .then(function (response) {
  531. expect(response).to.have.status(200);
  532. expect(response).to.have.json('ttl', 40);
  533. expect(response.body.records).to.have.members(['"bar"']);
  534. });
  535. });
  536. itShowsUpInPdnsAs('a.2', domain, 'TXT', ['"bar"'], 40);
  537. });
  538. describe("cannot bulk-put duplicate RRsets", function () {
  539. var response;
  540. before(function () {
  541. response = chakram.put(
  542. '/domains/' + domain + '/rrsets/',
  543. [
  544. {'subname': 'b.2', 'ttl': 60, 'type': 'TXT', 'records': ['"bar"']},
  545. {'subname': 'b.2', 'ttl': 60, 'type': 'TXT', 'records': ['"bar"']},
  546. ]
  547. );
  548. return expect(response).to.have.status(400);
  549. });
  550. it("gives the right response", function () {
  551. return expect(response).to.have.json([
  552. { },
  553. { '__all__': [ 'RRset repeated with same subname and type.' ] },
  554. ]);
  555. });
  556. it("does not touch records in the API", function () {
  557. return chakram
  558. .get('/domains/' + domain + '/rrsets/b.2.../TXT/')
  559. .then(function (response) {
  560. expect(response).to.have.status(200);
  561. expect(response).to.have.json('ttl', 50);
  562. expect(response.body.records).to.have.members(['"foo"']);
  563. });
  564. });
  565. itShowsUpInPdnsAs('b.2', domain, 'TXT', ['"foo"'], 50);
  566. });
  567. describe("can delete RRsets via bulk-put", function () {
  568. var response;
  569. before(function () {
  570. response = chakram.put(
  571. '/domains/' + domain + '/rrsets/',
  572. [
  573. {'subname': 'c.2', 'ttl': 40, 'type': 'A', 'records': []},
  574. ]
  575. );
  576. return expect(response).to.have.status(200);
  577. });
  578. it("gives the right response", function () {
  579. var response = chakram.get('/domains/' + domain + '/rrsets/c.2.../A/');
  580. return expect(response).to.have.status(404);
  581. });
  582. });
  583. });
  584. describe("cannot bulk-put with invalid input", function () {
  585. it("gives the right response for invalid type", function () {
  586. var response = chakram.put(
  587. '/domains/' + domain + '/rrsets/',
  588. [{'subname': 'a.2', 'ttl': 50, 'type': 'INVALID', 'records': ['"foo"']}]
  589. );
  590. return expect(response).to.have.status(422);
  591. });
  592. it("gives the right response for invalid records", function () {
  593. var response = chakram.put(
  594. '/domains/' + domain + '/rrsets/',
  595. [{'subname': 'a.2', 'ttl': 50, 'type': 'MX', 'records': ['1.2.3.4']}]
  596. );
  597. return expect(response).to.have.status(422);
  598. });
  599. });
  600. });
  601. describe('PATCH rrsets/ with fresh domain', function () {
  602. var domain = 'e2etest-' + require("uuid").v4() + '.dedyn.io';
  603. before(function () {
  604. return expect(chakram.post('/domains/', {'name': domain})).to.have.status(201);
  605. });
  606. describe("can modify a single existing RRset using PATCH", function () {
  607. before(function () {
  608. var response = chakram.post(
  609. '/domains/' + domain + '/rrsets/',
  610. { 'subname': 'single', 'type': 'AAAA', 'records': ['bade::fefe'], 'ttl': 62 }
  611. ).then(function () {
  612. return chakram.patch(
  613. '/domains/' + domain + '/rrsets/single.../AAAA/',
  614. { 'records': ['fefe::bade'], 'ttl': 31 }
  615. );
  616. });
  617. expect(response).to.have.status(200);
  618. expect(response).to.have.schema(schemas.rrset);
  619. return chakram.wait();
  620. });
  621. itPropagatesToTheApi([
  622. {subname: 'single', domain: domain, type: 'AAAA', ttl: 31, records: ['fefe::bade']},
  623. ]);
  624. itShowsUpInPdnsAs('single', domain, 'AAAA', ['fefe::bade'], 31);
  625. });
  626. describe("can bulk-patch an AAAA and an MX record", function () {
  627. before(function () {
  628. var response = chakram.patch(
  629. '/domains/' + domain + '/rrsets/',
  630. [
  631. { 'subname': 'ipv6', 'type': 'AAAA', 'records': ['dead::beef'], 'ttl': 22 },
  632. { /* implied: 'subname': '', */ 'type': 'MX', 'records': ['10 mail.example.com.', '20 mail.example.net.'], 'ttl': 33 }
  633. ]
  634. );
  635. expect(response).to.have.status(200);
  636. expect(response).to.have.schema(schemas.rrsets);
  637. return chakram.wait();
  638. });
  639. itPropagatesToTheApi([
  640. {subname: 'ipv6', domain: domain, type: 'AAAA', ttl: 22, records: ['dead::beef']},
  641. {subname: '', domain: domain, type: 'MX', ttl: 33, records: ['10 mail.example.com.', '20 mail.example.net.']},
  642. ]);
  643. itShowsUpInPdnsAs('ipv6', domain, 'AAAA', ['dead::beef'], 22);
  644. itShowsUpInPdnsAs('', domain, 'MX', ['10 mail.example.com.', '20 mail.example.net.'], 33);
  645. });
  646. describe("cannot bulk-patch with missing or invalid fields", function () {
  647. before(function () {
  648. // Set an RRset that we'll try to overwrite
  649. var response = chakram.post(
  650. '/domains/' + domain + '/rrsets/',
  651. [{'ttl': 50, 'type': 'TXT', 'records': ['"foo"']}]
  652. );
  653. expect(response).to.have.status(201);
  654. var response = chakram.patch(
  655. '/domains/' + domain + '/rrsets/',
  656. [
  657. {'subname': 'a.1', 'records': ['dead::beef'], 'ttl': 22},
  658. {'subname': 'b.1', 'ttl': -50, 'type': 'AAAA', 'records': ['dead::beef']},
  659. {'ttl': 40, 'type': 'TXT', 'records': ['"bar"']},
  660. {'subname': 'c.1', 'records': ['dead::beef'], 'type': 'AAAA'},
  661. {'subname': 'd.1', 'ttl': 50, 'type': 'AAAA'},
  662. ]
  663. );
  664. expect(response).to.have.status(400);
  665. expect(response).to.have.json([
  666. { type: [ 'This field is required.' ] },
  667. { ttl: [ 'Ensure this value is greater than or equal to 1.' ] },
  668. {},
  669. {},
  670. {},
  671. ]);
  672. return chakram.wait();
  673. });
  674. it("does not propagate partially to the API", function () {
  675. return chakram.waitFor([
  676. chakram
  677. .get('/domains/' + domain + '/rrsets/b.1.../AAAA/')
  678. .then(function (response) {
  679. expect(response).to.have.status(404);
  680. }),
  681. chakram
  682. .get('/domains/' + domain + '/rrsets/.../TXT/')
  683. .then(function (response) {
  684. expect(response).to.have.status(200);
  685. expect(response).to.have.json('ttl', 50);
  686. expect(response.body.records).to.have.members(['"foo"']);
  687. }),
  688. ]);
  689. });
  690. itShowsUpInPdnsAs('b.1', domain, 'AAAA', []);
  691. });
  692. context("with a pre-existing RRset", function () {
  693. before(function () {
  694. var response = chakram.post(
  695. '/domains/' + domain + '/rrsets/',
  696. [
  697. {'subname': 'a.1', 'ttl': 50, 'type': 'TXT', 'records': ['"foo"']},
  698. {'subname': 'a.2', 'ttl': 50, 'type': 'A', 'records': ['4.3.2.1']},
  699. {'subname': 'a.2', 'ttl': 50, 'type': 'TXT', 'records': ['"foo"']},
  700. {'subname': 'b.2', 'ttl': 50, 'type': 'A', 'records': ['5.4.3.2']},
  701. {'subname': 'b.2', 'ttl': 50, 'type': 'TXT', 'records': ['"foo"']},
  702. {'subname': 'c.2', 'ttl': 50, 'type': 'A', 'records': ['1.2.3.4']},
  703. ]
  704. );
  705. return expect(response).to.have.status(201);
  706. });
  707. describe("can bulk-patch existing RRsets", function () {
  708. var response;
  709. before(function () {
  710. response = chakram.patch(
  711. '/domains/' + domain + '/rrsets/',
  712. [
  713. {'subname': 'a.1', 'type': 'TXT', 'records': ['"bar"']},
  714. {'subname': 'a.2', 'ttl': 40, 'type': 'TXT', 'records': ['"bar"']},
  715. ]
  716. );
  717. expect(response).to.have.status(200);
  718. expect(response).to.have.schema(schemas.rrsets);
  719. return chakram.wait();
  720. });
  721. it("does modify records in the API", function () {
  722. return chakram.waitFor([
  723. chakram
  724. .get('/domains/' + domain + '/rrsets/a.1.../TXT/')
  725. .then(function (response) {
  726. expect(response).to.have.status(200);
  727. expect(response).to.have.json('ttl', 50);
  728. expect(response.body.records).to.have.members(['"bar"']);
  729. }),
  730. chakram
  731. .get('/domains/' + domain + '/rrsets/a.2.../TXT/')
  732. .then(function (response) {
  733. expect(response).to.have.status(200);
  734. expect(response).to.have.json('ttl', 40);
  735. expect(response.body.records).to.have.members(['"bar"']);
  736. }),
  737. ]);
  738. });
  739. itShowsUpInPdnsAs('a.2', domain, 'TXT', ['"bar"'], 40);
  740. });
  741. describe("cannot bulk-patch duplicate RRsets", function () {
  742. var response;
  743. before(function () {
  744. response = chakram.patch(
  745. '/domains/' + domain + '/rrsets/',
  746. [
  747. {'subname': 'b.2', 'ttl': 40, 'type': 'TXT', 'records': ['"bar"']},
  748. {'subname': 'b.2', 'ttl': 40, 'type': 'TXT', 'records': ['"bar"']},
  749. ]
  750. );
  751. return expect(response).to.have.status(400);
  752. });
  753. it("gives the right response", function () {
  754. return expect(response).to.have.json([
  755. {},
  756. { '__all__': [ 'RRset repeated with same subname and type.' ] },
  757. ]);
  758. });
  759. it("does not touch records in the API", function () {
  760. return chakram
  761. .get('/domains/' + domain + '/rrsets/b.2.../TXT/')
  762. .then(function (response) {
  763. expect(response).to.have.status(200);
  764. expect(response).to.have.json('ttl', 50);
  765. expect(response.body.records).to.have.members(['"foo"']);
  766. });
  767. });
  768. itShowsUpInPdnsAs('b.2', domain, 'TXT', ['"foo"'], 50);
  769. });
  770. describe("can delete RRsets via bulk-patch", function () {
  771. var response;
  772. before(function () {
  773. response = chakram.patch(
  774. '/domains/' + domain + '/rrsets/',
  775. [
  776. {'subname': 'c.2', 'type': 'A', 'records': []},
  777. ]
  778. );
  779. return expect(response).to.have.status(200);
  780. });
  781. it("gives the right response", function () {
  782. var response = chakram.get('/domains/' + domain + '/rrsets/c.2.../A/');
  783. return expect(response).to.have.status(404);
  784. });
  785. });
  786. describe("accepts missing fields for no-op requests via bulk-patch", function () {
  787. var response;
  788. before(function () {
  789. response = chakram.patch(
  790. '/domains/' + domain + '/rrsets/',
  791. [
  792. {'subname': 'a.2', 'type': 'A', 'records': ['6.6.6.6']}, // existing RRset; TTL not needed
  793. {'subname': 'b.2', 'type': 'A', 'ttl': 40}, // existing RRset; records not needed
  794. {'subname': 'x.2', 'type': 'A', 'records': []}, // non-existent, no-op
  795. {'subname': 'x.2', 'type': 'AAAA'}, // non-existent, no-op
  796. {'subname': 'x.2', 'type': 'TXT', 'ttl': 32}, // non-existent, no-op
  797. ]
  798. );
  799. return expect(response).to.have.status(200);
  800. });
  801. it("gives the right response", function () {
  802. var response = chakram.get('/domains/' + domain + '/rrsets/b.2.../A/');
  803. expect(response).to.have.status(200);
  804. expect(response).to.have.json('ttl', 40);
  805. return chakram.wait();
  806. });
  807. });
  808. describe("catches invalid type for no-op request via bulk-patch", function () {
  809. it("gives the right response", function () {
  810. return chakram.patch(
  811. '/domains/' + domain + '/rrsets/',
  812. [
  813. {'subname': 'x.2', 'type': 'AAA'}, // non-existent, no-op, but invalid type
  814. ]
  815. ).then(function (respObj) {
  816. expect(respObj).to.have.status(422);
  817. expect(respObj.body.detail).to.match(/IN AAA: unknown type given$/);
  818. return chakram.wait();
  819. });
  820. });
  821. });
  822. });
  823. describe("cannot bulk-patch with invalid input", function () {
  824. it("gives the right response for invalid type", function () {
  825. var response = chakram.patch(
  826. '/domains/' + domain + '/rrsets/',
  827. [{'subname': 'a.2', 'ttl': 50, 'type': 'INVALID', 'records': ['"foo"']}]
  828. );
  829. return expect(response).to.have.status(422);
  830. });
  831. it("gives the right response for invalid records", function () {
  832. var response = chakram.patch(
  833. '/domains/' + domain + '/rrsets/',
  834. [{'subname': 'a.2', 'ttl': 50, 'type': 'MX', 'records': ['1.2.3.4']}]
  835. );
  836. return expect(response).to.have.status(422);
  837. });
  838. });
  839. });
  840. describe("tokens/ endpoint", function () {
  841. var tokenId;
  842. var tokenValue;
  843. function createTokenWithName () {
  844. var tokenname = "e2e-token-" + require("uuid").v4();
  845. return chakram.post('/tokens/', { name: tokenname }).then(function (response) {
  846. expect(response).to.have.status(201);
  847. expect(response).to.have.json('name', tokenname);
  848. tokenId = response.body['id'];
  849. });
  850. }
  851. function createToken () {
  852. return chakram.post('/tokens/').then(function (response) {
  853. expect(response).to.have.status(201);
  854. tokenId = response.body['id'];
  855. tokenValue = response.body['value'];
  856. });
  857. }
  858. it("can create tokens", createToken);
  859. it("can create tokens with name", createTokenWithName)
  860. describe("with tokens", function () {
  861. before(createToken)
  862. it("a list of tokens can be retrieved", function () {
  863. var response = chakram.get('/tokens/');
  864. return expect(response).to.have.schema(schemas.tokens);
  865. });
  866. describe("can delete token", function () {
  867. before( function () {
  868. var response = chakram.delete('/tokens/' + tokenId + '/');
  869. return expect(response).to.have.status(204);
  870. });
  871. it("deactivates the token", function () {
  872. return expect(chakram.get('/tokens/', {
  873. headers: {'Authorization': 'Token ' + tokenValue }
  874. })).to.have.status(401);
  875. });
  876. });
  877. it("deleting nonexistent tokens yields 204", function () {
  878. var response = chakram.delete('/tokens/wedonthavethisid/');
  879. return expect(response).to.have.status(204);
  880. });
  881. });
  882. })
  883. });
  884. });
  885. });