subscribers.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322
  1. const apiUrl = Cypress.env('apiUrl');
  2. describe('Subscribers', () => {
  3. it('Opens subscribers page', () => {
  4. cy.resetDB();
  5. cy.loginAndVisit('/subscribers');
  6. });
  7. it('Counts subscribers', () => {
  8. cy.get('tbody td[data-label=Status]').its('length').should('eq', 2);
  9. });
  10. it('Searches subscribers', () => {
  11. const cases = [
  12. { value: 'john{enter}', count: 1, contains: 'john@example.com' },
  13. { value: 'anon{enter}', count: 1, contains: 'anon@example.com' },
  14. { value: '{enter}', count: 2, contains: null },
  15. ];
  16. cases.forEach((c) => {
  17. cy.get('[data-cy=search]').clear().type(c.value);
  18. cy.get('tbody td[data-label=Status]').its('length').should('eq', c.count);
  19. if (c.contains) {
  20. cy.get('tbody td[data-label=E-mail]').contains(c.contains);
  21. }
  22. });
  23. });
  24. it('Advanced searches subscribers', () => {
  25. cy.get('[data-cy=btn-advanced-search]').click();
  26. const cases = [
  27. { value: 'subscribers.attribs->>\'city\'=\'Bengaluru\'', count: 2 },
  28. { value: 'subscribers.attribs->>\'city\'=\'Bengaluru\' AND id=1', count: 1 },
  29. { value: '(subscribers.attribs->>\'good\')::BOOLEAN = true AND name like \'Anon%\'', count: 1 },
  30. ];
  31. cases.forEach((c) => {
  32. cy.get('[data-cy=query]').clear().type(c.value);
  33. cy.get('[data-cy=btn-query]').click();
  34. cy.get('tbody td[data-label=Status]').its('length').should('eq', c.count);
  35. });
  36. cy.get('[data-cy=btn-query-reset]').click();
  37. cy.wait(1000);
  38. cy.get('tbody td[data-label=Status]').its('length').should('eq', 2);
  39. });
  40. it('Does bulk subscriber list add and remove', () => {
  41. const cases = [
  42. // radio: action to perform, rows: table rows to select and perform on: [expected statuses of those rows after thea action]
  43. { radio: 'check-list-add', lists: [0, 1], rows: { 0: ['unconfirmed', 'unconfirmed'] } },
  44. { radio: 'check-list-unsubscribe', lists: [0, 1], rows: { 0: ['unsubscribed', 'unsubscribed'], 1: ['unsubscribed'] } },
  45. { radio: 'check-list-remove', lists: [0, 1], rows: { 1: [] } },
  46. { radio: 'check-list-add', lists: [0, 1], rows: { 0: ['unsubscribed', 'unsubscribed'], 1: ['unconfirmed', 'unconfirmed'] } },
  47. { radio: 'check-list-remove', lists: [0], rows: { 0: ['unsubscribed'] } },
  48. { radio: 'check-list-add', lists: [0], rows: { 0: ['unconfirmed', 'unsubscribed'] } },
  49. ];
  50. cases.forEach((c, n) => {
  51. // Select one of the 2 subscribers in the table.
  52. Object.keys(c.rows).forEach((r) => {
  53. cy.get('tbody td.checkbox-cell .checkbox').eq(r).click();
  54. });
  55. // Open the 'manage lists' modal.
  56. cy.get('[data-cy=btn-manage-lists]').click();
  57. // Check both lists in the modal.
  58. c.lists.forEach((l) => {
  59. cy.get('.list-selector input').click();
  60. cy.get('.list-selector .autocomplete a').first().click();
  61. });
  62. // Select the radio option in the modal.
  63. cy.get(`[data-cy=${c.radio}] .check`).click();
  64. // Save.
  65. cy.get('.modal button.is-primary').click();
  66. // Check the status of the lists on the subscriber.
  67. Object.keys(c.rows).forEach((r) => {
  68. cy.get('tbody td[data-label=E-mail]').eq(r).find('.tags').then(($el) => {
  69. cy.wrap($el).find('.tag').should('have.length', c.rows[r].length);
  70. c.rows[r].forEach((status, n) => {
  71. // eg: .tag(n).unconfirmed
  72. cy.wrap($el).find('.tag').eq(n).should('have.class', status);
  73. });
  74. });
  75. });
  76. });
  77. });
  78. it('Resets subscribers page', () => {
  79. cy.resetDB();
  80. cy.loginAndVisit('/subscribers');
  81. });
  82. it('Edits subscribers', () => {
  83. const status = ['enabled', 'blocklisted'];
  84. const json = '{"string": "hello", "ints": [1,2,3], "null": null, "sub": {"bool": true}}';
  85. // Collect values being edited on each sub to confirm the changes in the next step
  86. // index by their ID shown in the modal.
  87. const rows = {};
  88. // Open the edit popup and edit the default lists.
  89. cy.get('[data-cy=btn-edit]').each(($el, n) => {
  90. const email = `email-${n}@EMAIL.com`;
  91. const name = `name-${n}`;
  92. // Open the edit modal.
  93. cy.wrap($el).click();
  94. // Get the ID from the header and proceed to fill the form.
  95. let id = 0;
  96. cy.get('[data-cy=id]').then(($el) => {
  97. id = $el.text();
  98. cy.get('input[name=email]').clear().type(email);
  99. cy.get('input[name=name]').clear().type(name);
  100. cy.get('select[name=status]').select(status[n]);
  101. cy.get('.list-selector input').click();
  102. cy.get('.list-selector .autocomplete a').first().click();
  103. cy.get('textarea[name=attribs]').clear().type(json, { parseSpecialCharSequences: false, delay: 0 });
  104. cy.get('.modal-card-foot button[type=submit]').click();
  105. rows[id] = { email, name, status: status[n] };
  106. });
  107. });
  108. // Confirm the edits on the table.
  109. cy.wait(250);
  110. cy.get('tbody tr').each(($el) => {
  111. cy.wrap($el).find('td[data-id]').invoke('attr', 'data-id').then((id) => {
  112. cy.wrap($el).find('td[data-label=E-mail]').contains(rows[id].email.toLowerCase());
  113. cy.wrap($el).find('td[data-label=Name]').contains(rows[id].name);
  114. cy.wrap($el).find('td[data-label=Status]').contains(rows[id].status, { matchCase: false });
  115. // Both lists on the enabled sub should be 'unconfirmed' and the blocklisted one, 'unsubscribed.'
  116. cy.wrap($el).find(`.tags .${rows[id].status === 'enabled' ? 'unconfirmed' : 'unsubscribed'}`)
  117. .its('length').should('eq', 2);
  118. cy.wrap($el).find('td[data-label=Lists]').then((l) => {
  119. cy.expect(parseInt(l.text().trim())).to.equal(rows[id].status === 'blocklisted' ? 0 : 2);
  120. });
  121. });
  122. });
  123. });
  124. it('Deletes subscribers', () => {
  125. // Delete all visible lists.
  126. cy.get('tbody tr').each(() => {
  127. cy.get('tbody a[data-cy=btn-delete]').first().click();
  128. cy.get('.modal button.is-primary').click();
  129. });
  130. // Confirm deletion.
  131. cy.get('table tr.is-empty');
  132. });
  133. it('Creates new subscribers', () => {
  134. const statuses = ['enabled', 'blocklisted'];
  135. const lists = [[1], [2], [1, 2]];
  136. const json = '{"string": "hello", "ints": [1,2,3], "null": null, "sub": {"bool": true}}';
  137. // Cycle through each status and each list ID combination and create subscribers.
  138. const n = 0;
  139. for (let n = 0; n < 6; n++) {
  140. const email = `email-${n}@EMAIL.com`;
  141. const name = `name-${n}`;
  142. const status = statuses[(n + 1) % statuses.length];
  143. const list = lists[(n + 1) % lists.length];
  144. cy.get('[data-cy=btn-new]').click();
  145. cy.get('input[name=email]').type(email);
  146. cy.get('input[name=name]').type(name);
  147. cy.get('select[name=status]').select(status);
  148. list.forEach((l) => {
  149. cy.get('.list-selector input').click();
  150. cy.get('.list-selector .autocomplete a').first().click();
  151. });
  152. cy.get('textarea[name=attribs]').clear().type(json, { parseSpecialCharSequences: false, delay: 0 });
  153. cy.get('.modal-card-foot button[type=submit]').click();
  154. // Confirm the addition by inspecting the newly created list row,
  155. // which is always the first row in the table.
  156. cy.wait(250);
  157. const tr = cy.get('tbody tr:nth-child(1)').then(($el) => {
  158. cy.wrap($el).find('td[data-label=E-mail]').contains(email.toLowerCase());
  159. cy.wrap($el).find('td[data-label=Name]').contains(name);
  160. cy.wrap($el).find('td[data-label=Status]').contains(status, { matchCase: false });
  161. cy.wrap($el).find(`.tags .${status === 'enabled' ? 'unconfirmed' : 'unsubscribed'}`)
  162. .its('length').should('eq', list.length);
  163. cy.wrap($el).find('td[data-label=Lists]').then((l) => {
  164. cy.expect(parseInt(l.text().trim())).to.equal(status === 'blocklisted' ? 0 : list.length);
  165. });
  166. });
  167. }
  168. });
  169. it('Sorts subscribers', () => {
  170. const asc = [3, 4, 5, 6, 7, 8];
  171. const desc = [8, 7, 6, 5, 4, 3];
  172. const cases = ['cy-status', 'cy-email', 'cy-name', 'cy-created_at', 'cy-updated_at'];
  173. cases.forEach((c) => {
  174. cy.sortTable(`thead th.${c}`, asc);
  175. cy.wait(250);
  176. cy.sortTable(`thead th.${c}`, desc);
  177. cy.wait(250);
  178. });
  179. });
  180. });
  181. describe('Domain blocklist', () => {
  182. it('Opens settings page', () => {
  183. cy.resetDB();
  184. });
  185. it('Add domains to blocklist', () => {
  186. cy.loginAndVisit('/settings');
  187. cy.get('.b-tabs nav a').eq(2).click();
  188. cy.get('textarea[name="privacy.domain_blocklist"]').clear().type('ban.net\n\nBaN.OrG\n\nban.com\n\n');
  189. cy.get('[data-cy=btn-save]').click();
  190. });
  191. it('Try subscribing via public page', () => {
  192. cy.visit(`${apiUrl}/subscription/form`);
  193. cy.get('input[name=email]').clear().type('test@noban.net');
  194. cy.get('button[type=submit]').click();
  195. cy.get('h2').contains('Subscribe');
  196. cy.visit(`${apiUrl}/subscription/form`);
  197. cy.get('input[name=email]').clear().type('test@ban.net');
  198. cy.get('button[type=submit]').click();
  199. cy.get('h2').contains('Error');
  200. });
  201. // Post to the admin API.
  202. it('Try via admin API', () => {
  203. cy.wait(1000);
  204. // Add non-banned domain.
  205. cy.request({
  206. method: 'POST', url: `${apiUrl}/api/subscribers`, failOnStatusCode: true,
  207. body: { email: 'test1@noban.net', 'name': 'test', 'lists': [1], 'status': 'enabled' }
  208. }).should((response) => {
  209. expect(response.status).to.equal(200);
  210. });
  211. // Add banned domain.
  212. cy.request({
  213. method: 'POST', url: `${apiUrl}/api/subscribers`, failOnStatusCode: false,
  214. body: { email: 'test1@ban.com', 'name': 'test', 'lists': [1], 'status': 'enabled' }
  215. }).should((response) => {
  216. expect(response.status).to.equal(400);
  217. });
  218. // Modify an existinb subscriber to a banned domain.
  219. cy.request({
  220. method: 'PUT', url: `${apiUrl}/api/subscribers/1`, failOnStatusCode: false,
  221. body: { email: 'test3@ban.org', 'name': 'test', 'lists': [1], 'status': 'enabled' }
  222. }).should((response) => {
  223. expect(response.status).to.equal(400);
  224. });
  225. });
  226. it('Try via import', () => {
  227. cy.loginAndVisit('/subscribers/import');
  228. cy.get('.list-selector input').click();
  229. cy.get('.list-selector .autocomplete a').first().click();
  230. cy.fixture('subs-domain-blocklist.csv').then((data) => {
  231. cy.get('input[type="file"]').attachFile({
  232. fileContent: data.toString(),
  233. fileName: 'subs.csv',
  234. mimeType: 'text/csv',
  235. });
  236. });
  237. cy.get('button.is-primary').click();
  238. cy.get('section.wrap .has-text-success');
  239. // cy.get('button.is-primary').click();
  240. cy.get('.log-view').should('contain', 'ban1-import@BAN.net').and('contain', 'ban2-import@ban.ORG');
  241. cy.wait(100);
  242. });
  243. it('Clear blocklist and try', () => {
  244. cy.loginAndVisit('/settings');
  245. cy.get('.b-tabs nav a').eq(2).click();
  246. cy.get('textarea[name="privacy.domain_blocklist"]').clear();
  247. cy.get('[data-cy=btn-save]').click();
  248. cy.wait(1000);
  249. // Add banned domain.
  250. cy.request({
  251. method: 'POST', url: `${apiUrl}/api/subscribers`, failOnStatusCode: true,
  252. body: { email: 'test4@BAN.com', 'name': 'test', 'lists': [1], 'status': 'enabled' }
  253. }).should((response) => {
  254. expect(response.status).to.equal(200);
  255. });
  256. // Modify an existinb subscriber to a banned domain.
  257. cy.request({
  258. method: 'PUT', url: `${apiUrl}/api/subscribers/1`, failOnStatusCode: true,
  259. body: { email: 'test4@BAN.org', 'name': 'test', 'lists': [1], 'status': 'enabled' }
  260. }).should((response) => {
  261. expect(response.status).to.equal(200);
  262. });
  263. });
  264. });