api_spec.js 51 KB

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