From 527cb01680a736846cf1e061fc587d3322410978 Mon Sep 17 00:00:00 2001 From: Hannah Wolfe Date: Mon, 7 Feb 2022 12:51:21 +0000 Subject: [PATCH] Updated members tests to use snapshot testing refs: https://github.com/TryGhost/Toolbox/issues/158 - This is a reference suite - it shows how snapshot testing can be used on a larger suite --- .../admin/__snapshots__/members.test.js.snap | 937 ++++++++++++++++++ test/regression/api/admin/members.test.js | 335 +++---- test/utils/e2e-framework.js | 1 + yarn.lock | 10 +- 4 files changed, 1094 insertions(+), 189 deletions(-) create mode 100644 test/regression/api/admin/__snapshots__/members.test.js.snap diff --git a/test/regression/api/admin/__snapshots__/members.test.js.snap b/test/regression/api/admin/__snapshots__/members.test.js.snap new file mode 100644 index 0000000000..9fc642a99b --- /dev/null +++ b/test/regression/api/admin/__snapshots__/members.test.js.snap @@ -0,0 +1,937 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Members API Add should fail when comped flag is passed in but Stripe is not enabled 1: [headers] 1`] = ` +Object { + "access-control-allow-origin": "http://127.0.0.1:2369", + "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", + "content-length": "376", + "content-type": "application/json; charset=utf-8", + "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, + "vary": "Origin, Accept-Encoding", + "x-powered-by": "Express", +} +`; + +exports[`Members API Add should fail when comped flag is passed in but Stripe is not enabled 2: [body] 1`] = ` +Object { + "errors": Array [ + Object { + "code": null, + "context": "Missing Stripe connection. Attempting to import members with Stripe data when there is no Stripe account connected.", + "details": null, + "help": "You need to connect to Stripe to import Stripe customers. ", + "id": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + "message": "Validation error, cannot save member.", + "property": "comped", + "type": "ValidationError", + }, + ], +} +`; + +exports[`Members API Add should fail when passing incorrect email_type query parameter 1: [headers] 1`] = ` +Object { + "access-control-allow-origin": "http://127.0.0.1:2369", + "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", + "content-length": "249", + "content-type": "application/json; charset=utf-8", + "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, + "vary": "Origin, Accept-Encoding", + "x-powered-by": "Express", +} +`; + +exports[`Members API Add should fail when passing incorrect email_type query parameter 2: [body] 1`] = ` +Object { + "errors": Array [ + Object { + "code": null, + "context": "Validation (AllowedValues) failed for email_type", + "details": null, + "help": null, + "id": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + "message": "Validation error, cannot save member.", + "property": null, + "type": "ValidationError", + }, + ], +} +`; + +exports[`Members API Can add and send a signup confirmation email 1: [body] 1`] = ` +Object { + "members": Array [ + Object { + "avatar_image": null, + "comped": false, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "email": "member_getting_confirmation@test.com", + "email_count": 0, + "email_open_rate": null, + "email_opened_count": 0, + "geolocation": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "labels": Array [], + "name": "Send Me Confirmation", + "note": null, + "products": Array [], + "status": "free", + "subscribed": true, + "subscriptions": Array [], + "updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + ], +} +`; + +exports[`Members API Can add and send a signup confirmation email 2: [headers] 1`] = ` +Object { + "access-control-allow-origin": "http://127.0.0.1:2369", + "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", + "content-length": "457", + "content-type": "application/json; charset=utf-8", + "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, + "location": Any, + "vary": "Origin, Accept-Encoding", + "x-powered-by": "Express", +} +`; + +exports[`Members API Can add and send a signup confirmation email 3: [headers] 1`] = ` +Object { + "access-control-allow-origin": "http://127.0.0.1:2369", + "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", + "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, + "vary": "Origin", + "x-powered-by": "Express", +} +`; + +exports[`Members API Can delete a member without cancelling Stripe Subscription 1: [headers] 1`] = ` +Object { + "access-control-allow-origin": "http://127.0.0.1:2369", + "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", + "content-length": "438", + "content-type": "application/json; charset=utf-8", + "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, + "location": Any, + "vary": "Origin, Accept-Encoding", + "x-powered-by": "Express", +} +`; + +exports[`Members API Can delete a member without cancelling Stripe Subscription 2: [body] 1`] = ` +Object { + "members": Array [ + Object { + "avatar_image": null, + "comped": false, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "email": "Member2Delete@test.com", + "email_count": 0, + "email_open_rate": null, + "email_opened_count": 0, + "geolocation": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "labels": Array [], + "name": "Member 2 Delete", + "note": null, + "products": Array [], + "status": "free", + "subscribed": true, + "subscriptions": Array [], + "updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + ], +} +`; + +exports[`Members API Can delete a member without cancelling Stripe Subscription 3: [headers] 1`] = ` +Object { + "access-control-allow-origin": "http://127.0.0.1:2369", + "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", + "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, + "vary": "Origin", + "x-powered-by": "Express", +} +`; + +exports[`Members API Can delete a member without cancelling Stripe Subscription 4: [body] 1`] = `Object {}`; + +exports[`Members API Can order by email_open_rate 1: [headers] 1`] = ` +Object { + "access-control-allow-origin": "http://127.0.0.1:2369", + "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", + "content-length": "7008", + "content-type": "application/json; charset=utf-8", + "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, + "vary": "Origin, Accept-Encoding", + "x-powered-by": "Express", +} +`; + +exports[`Members API Can order by email_open_rate 2: [body] 1`] = ` +Object { + "members": Array [ + Object { + "avatar_image": null, + "comped": false, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "email": "paid@test.com", + "email_count": 0, + "email_open_rate": 80, + "email_opened_count": 0, + "geolocation": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "labels": Any, + "name": "Egon Spengler", + "note": null, + "status": "paid", + "subscribed": true, + "subscriptions": Any, + "updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + Object { + "avatar_image": null, + "comped": false, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "email": "member2@test.com", + "email_count": 0, + "email_open_rate": 50, + "email_opened_count": 0, + "geolocation": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "labels": Any, + "name": null, + "note": null, + "status": "free", + "subscribed": true, + "subscriptions": Any, + "updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + Object { + "avatar_image": null, + "comped": false, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "email": "member1@test.com", + "email_count": 0, + "email_open_rate": null, + "email_opened_count": 0, + "geolocation": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "labels": Any, + "name": "Mr Egg", + "note": null, + "status": "free", + "subscribed": true, + "subscriptions": Any, + "updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + Object { + "avatar_image": null, + "comped": false, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "email": "trialing@test.com", + "email_count": 0, + "email_open_rate": null, + "email_opened_count": 0, + "geolocation": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "labels": Any, + "name": "Ray Stantz", + "note": null, + "status": "paid", + "subscribed": true, + "subscriptions": Any, + "updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + Object { + "avatar_image": null, + "comped": false, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "email": "comped@test.com", + "email_count": 0, + "email_open_rate": null, + "email_opened_count": 0, + "geolocation": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "labels": Any, + "name": "Vinz Clortho", + "note": null, + "status": "paid", + "subscribed": true, + "subscriptions": Any, + "updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + Object { + "avatar_image": null, + "comped": false, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "email": "vip@test.com", + "email_count": 0, + "email_open_rate": null, + "email_opened_count": 0, + "geolocation": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "labels": Any, + "name": "Winston Zeddemore", + "note": null, + "status": "free", + "subscribed": true, + "subscriptions": Any, + "updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + Object { + "avatar_image": null, + "comped": false, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "email": "vip-paid@test.com", + "email_count": 0, + "email_open_rate": null, + "email_opened_count": 0, + "geolocation": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "labels": Any, + "name": "Peter Venkman", + "note": null, + "status": "paid", + "subscribed": true, + "subscriptions": Any, + "updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + Object { + "avatar_image": null, + "comped": false, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "email": "with-product@test.com", + "email_count": 0, + "email_open_rate": null, + "email_opened_count": 0, + "geolocation": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "labels": Any, + "name": "Dana Barrett", + "note": null, + "status": "paid", + "subscribed": true, + "subscriptions": Any, + "updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + ], + "meta": Object { + "pagination": Object { + "limit": 15, + "next": null, + "page": 1, + "pages": 1, + "prev": null, + "total": 8, + }, + }, +} +`; + +exports[`Members API Can order by email_open_rate 3: [headers] 1`] = ` +Object { + "access-control-allow-origin": "http://127.0.0.1:2369", + "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", + "content-length": "7008", + "content-type": "application/json; charset=utf-8", + "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, + "vary": "Origin, Accept-Encoding", + "x-powered-by": "Express", +} +`; + +exports[`Members API Can order by email_open_rate 4: [body] 1`] = ` +Object { + "members": Array [ + Object { + "avatar_image": null, + "comped": false, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "email": "member2@test.com", + "email_count": 0, + "email_open_rate": 50, + "email_opened_count": 0, + "geolocation": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "labels": Any, + "name": null, + "note": null, + "status": "free", + "subscribed": true, + "subscriptions": Any, + "updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + Object { + "avatar_image": null, + "comped": false, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "email": "paid@test.com", + "email_count": 0, + "email_open_rate": 80, + "email_opened_count": 0, + "geolocation": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "labels": Any, + "name": "Egon Spengler", + "note": null, + "status": "paid", + "subscribed": true, + "subscriptions": Any, + "updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + Object { + "avatar_image": null, + "comped": false, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "email": "member1@test.com", + "email_count": 0, + "email_open_rate": null, + "email_opened_count": 0, + "geolocation": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "labels": Any, + "name": "Mr Egg", + "note": null, + "status": "free", + "subscribed": true, + "subscriptions": Any, + "updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + Object { + "avatar_image": null, + "comped": false, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "email": "trialing@test.com", + "email_count": 0, + "email_open_rate": null, + "email_opened_count": 0, + "geolocation": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "labels": Any, + "name": "Ray Stantz", + "note": null, + "status": "paid", + "subscribed": true, + "subscriptions": Any, + "updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + Object { + "avatar_image": null, + "comped": false, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "email": "comped@test.com", + "email_count": 0, + "email_open_rate": null, + "email_opened_count": 0, + "geolocation": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "labels": Any, + "name": "Vinz Clortho", + "note": null, + "status": "paid", + "subscribed": true, + "subscriptions": Any, + "updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + Object { + "avatar_image": null, + "comped": false, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "email": "vip@test.com", + "email_count": 0, + "email_open_rate": null, + "email_opened_count": 0, + "geolocation": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "labels": Any, + "name": "Winston Zeddemore", + "note": null, + "status": "free", + "subscribed": true, + "subscriptions": Any, + "updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + Object { + "avatar_image": null, + "comped": false, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "email": "vip-paid@test.com", + "email_count": 0, + "email_open_rate": null, + "email_opened_count": 0, + "geolocation": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "labels": Any, + "name": "Peter Venkman", + "note": null, + "status": "paid", + "subscribed": true, + "subscriptions": Any, + "updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + Object { + "avatar_image": null, + "comped": false, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "email": "with-product@test.com", + "email_count": 0, + "email_open_rate": null, + "email_opened_count": 0, + "geolocation": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "labels": Any, + "name": "Dana Barrett", + "note": null, + "status": "paid", + "subscribed": true, + "subscriptions": Any, + "updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + ], + "meta": Object { + "pagination": Object { + "limit": 15, + "next": null, + "page": 1, + "pages": 1, + "prev": null, + "total": 8, + }, + }, +} +`; + +exports[`Members API Can search by case-insensitive email 1: [body] 1`] = ` +Object { + "members": Array [ + Object { + "avatar_image": null, + "comped": false, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "email": "member2@test.com", + "email_count": 0, + "email_open_rate": 50, + "email_opened_count": 0, + "geolocation": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "labels": Array [], + "name": null, + "note": null, + "status": "free", + "subscribed": true, + "subscriptions": Array [], + "updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + ], + "meta": Object { + "pagination": Object { + "limit": 15, + "next": null, + "page": 1, + "pages": 1, + "prev": null, + "total": 1, + }, + }, +} +`; + +exports[`Members API Can search by case-insensitive email 2: [headers] 1`] = ` +Object { + "access-control-allow-origin": "http://127.0.0.1:2369", + "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", + "content-length": "491", + "content-type": "application/json; charset=utf-8", + "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, + "vary": "Origin, Accept-Encoding", + "x-powered-by": "Express", +} +`; + +exports[`Members API Can update a member with subscription included, change name to "Updated name" 1: [headers] 1`] = ` +Object { + "access-control-allow-origin": "http://127.0.0.1:2369", + "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", + "content-length": "1234", + "content-type": "application/json; charset=utf-8", + "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, + "vary": "Origin, Accept-Encoding", + "x-powered-by": "Express", +} +`; + +exports[`Members API Can update a member with subscription included, change name to "Updated name" 2: [body] 1`] = ` +Object { + "members": Array [ + Object { + "avatar_image": null, + "comped": false, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "email": "paid@test.com", + "email_count": 0, + "email_open_rate": 80, + "email_opened_count": 0, + "geolocation": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "labels": Any, + "name": "Updated name", + "note": null, + "products": Array [], + "status": "paid", + "subscribed": true, + "subscriptions": Any, + "updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + ], +} +`; + +exports[`Members API Can update a member with subscription included, change name to "Updated name" 3: [headers] 1`] = ` +Object { + "access-control-allow-origin": "http://127.0.0.1:2369", + "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", + "content-length": "1234", + "content-type": "application/json; charset=utf-8", + "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, + "vary": "Origin, Accept-Encoding", + "x-powered-by": "Express", +} +`; + +exports[`Members API Can update a member with subscription included, change name to "Updated name" 4: [body] 1`] = ` +Object { + "members": Array [ + Object { + "avatar_image": null, + "comped": false, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "email": "paid@test.com", + "email_count": 0, + "email_open_rate": 80, + "email_opened_count": 0, + "geolocation": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "labels": Any, + "name": "Updated name", + "note": null, + "products": Array [], + "status": "paid", + "subscribed": true, + "subscriptions": Any, + "updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + ], +} +`; + +exports[`Members API Can update a member with subscription included, change name to updated name 1: [headers] 1`] = ` +Object { + "access-control-allow-origin": "http://127.0.0.1:2369", + "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", + "content-length": "1296", + "content-type": "application/json; charset=utf-8", + "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, + "vary": "Origin, Accept-Encoding", + "x-powered-by": "Express", +} +`; + +exports[`Members API Can update a member with subscription included, change name to updated name 2: [body] 1`] = ` +Object { + "members": Array [ + Object { + "avatar_image": null, + "comped": false, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "email": "paid@test.com", + "email_count": 0, + "email_open_rate": 80, + "email_opened_count": 0, + "geolocation": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "labels": Any, + "name": "Egon Spengler", + "note": null, + "status": "paid", + "subscribed": true, + "subscriptions": Any, + "updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + ], + "meta": Object { + "pagination": Object { + "limit": 15, + "next": null, + "page": 1, + "pages": 1, + "prev": null, + "total": 1, + }, + }, +} +`; + +exports[`Members API Can update a member with subscription included, change name to updated name 3: [headers] 1`] = ` +Object { + "access-control-allow-origin": "http://127.0.0.1:2369", + "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", + "content-length": "1234", + "content-type": "application/json; charset=utf-8", + "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, + "vary": "Origin, Accept-Encoding", + "x-powered-by": "Express", +} +`; + +exports[`Members API Can update a member with subscription included, change name to updated name 4: [body] 1`] = ` +Object { + "members": Array [ + Object { + "avatar_image": null, + "comped": false, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "email": "paid@test.com", + "email_count": 0, + "email_open_rate": 80, + "email_opened_count": 0, + "geolocation": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "labels": Any, + "name": "Updated name", + "note": null, + "products": Array [], + "status": "paid", + "subscribed": true, + "subscriptions": Any, + "updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + ], +} +`; + +exports[`Members API Errors when fetching stats with unknown days param value 1: [headers] 1`] = ` +Object { + "access-control-allow-origin": "http://127.0.0.1:2369", + "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", + "content-length": "248", + "content-type": "application/json; charset=utf-8", + "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, + "vary": "Origin, Accept-Encoding", + "x-powered-by": "Express", +} +`; + +exports[`Members API Errors when fetching stats with unknown days param value 2: [body] 1`] = ` +Object { + "errors": Array [ + Object { + "code": null, + "context": "Validation (matches) failed for id undefined.id", + "details": null, + "help": null, + "id": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + "message": "Validation error, cannot read member.", + "property": null, + "type": "ValidationError", + }, + ], +} +`; + +exports[`Members API Sarch by case-insensitive name egg receives member with name Mr Egg 1: [body] 1`] = ` +Object { + "members": Array [ + Object { + "avatar_image": null, + "comped": false, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "email": "member1@test.com", + "email_count": 0, + "email_open_rate": null, + "email_opened_count": 0, + "geolocation": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "labels": Any, + "name": "Mr Egg", + "note": null, + "status": "free", + "subscribed": true, + "subscriptions": Any, + "updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + ], + "meta": Object { + "pagination": Object { + "limit": 15, + "next": null, + "page": 1, + "pages": 1, + "prev": null, + "total": 1, + }, + }, +} +`; + +exports[`Members API Sarch by case-insensitive name egg receives member with name Mr Egg 2: [headers] 1`] = ` +Object { + "access-control-allow-origin": "http://127.0.0.1:2369", + "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", + "content-length": "644", + "content-type": "application/json; charset=utf-8", + "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, + "vary": "Origin, Accept-Encoding", + "x-powered-by": "Express", +} +`; + +exports[`Members API Sarch for paid members retrieves member with email paid@test.com 1: [body] 1`] = ` +Object { + "members": Array [ + Object { + "avatar_image": null, + "comped": false, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "email": "paid@test.com", + "email_count": 0, + "email_open_rate": 80, + "email_opened_count": 0, + "geolocation": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "labels": Any, + "name": "Egon Spengler", + "note": null, + "status": "paid", + "subscribed": true, + "subscriptions": Any, + "updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + ], + "meta": Object { + "pagination": Object { + "limit": 15, + "next": null, + "page": 1, + "pages": 1, + "prev": null, + "total": 1, + }, + }, +} +`; + +exports[`Members API Sarch for paid members retrieves member with email paid@test.com 2: [headers] 1`] = ` +Object { + "access-control-allow-origin": "http://127.0.0.1:2369", + "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", + "content-length": "1296", + "content-type": "application/json; charset=utf-8", + "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, + "vary": "Origin, Accept-Encoding", + "x-powered-by": "Express", +} +`; + +exports[`Members API Search by case-insensitive email MEMBER2 receives member with email member2@test.com 1: [body] 1`] = ` +Object { + "members": Array [ + Object { + "avatar_image": null, + "comped": false, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "email": "member2@test.com", + "email_count": 0, + "email_open_rate": 50, + "email_opened_count": 0, + "geolocation": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "labels": Any, + "name": null, + "note": null, + "status": "free", + "subscribed": true, + "subscriptions": Any, + "updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + ], + "meta": Object { + "pagination": Object { + "limit": 15, + "next": null, + "page": 1, + "pages": 1, + "prev": null, + "total": 1, + }, + }, +} +`; + +exports[`Members API Search by case-insensitive email MEMBER2 receives member with email member2@test.com 2: [headers] 1`] = ` +Object { + "access-control-allow-origin": "http://127.0.0.1:2369", + "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", + "content-length": "491", + "content-type": "application/json; charset=utf-8", + "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, + "vary": "Origin, Accept-Encoding", + "x-powered-by": "Express", +} +`; + +exports[`Members API Search for non existing member returns empty result set 1: [headers] 1`] = ` +Object { + "access-control-allow-origin": "http://127.0.0.1:2369", + "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", + "content-length": "102", + "content-type": "application/json; charset=utf-8", + "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, + "vary": "Origin, Accept-Encoding", + "x-powered-by": "Express", +} +`; + +exports[`Members API Search for non existing member returns empty result set 2: [body] 1`] = ` +Object { + "members": Array [], + "meta": Object { + "pagination": Object { + "limit": 15, + "next": null, + "page": 1, + "pages": 1, + "prev": null, + "total": 0, + }, + }, +} +`; diff --git a/test/regression/api/admin/members.test.js b/test/regression/api/admin/members.test.js index b010c233a8..6d92a70793 100644 --- a/test/regression/api/admin/members.test.js +++ b/test/regression/api/admin/members.test.js @@ -1,12 +1,26 @@ const querystring = require('querystring'); -const should = require('should'); -const sinon = require('sinon'); -const testUtils = require('../../../utils'); -const {agentProvider, mockManager, fixtureManager} = require('../../../utils/e2e-framework'); -const localUtils = require('./utils'); +const assert = require('assert'); +const {agentProvider, mockManager, fixtureManager, matchers} = require('../../../utils/e2e-framework'); +const {anyString, anyArray, anyObjectId, anyEtag, anyUuid, anyErrorId, anyDate} = matchers; let agent; +const memberMatcherNoIncludes = { + id: anyObjectId, + uuid: anyUuid, + created_at: anyDate, + updated_at: anyDate +}; + +const memberMatcherShallowIncludes = { + id: anyObjectId, + uuid: anyUuid, + created_at: anyDate, + updated_at: anyDate, + subscriptions: anyArray, + labels: anyArray +}; + describe('Members API', function () { before(async function () { agent = await agentProvider.getAdminAPIAgent(); @@ -35,270 +49,215 @@ describe('Members API', function () { email_type: 'signup' }; - const res = await agent + const {body} = await agent .post(`members/?${querystring.stringify(queryParams)}`) .body({members: [member]}) - .expectHeader('Content-Type', /json/) - .expectHeader('Cache-Control', testUtils.cacheRules.private) - .expectStatus(201); - - should.not.exist(res.headers['x-cache-invalidate']); - const jsonResponse = res.body; - should.exist(jsonResponse); - should.exist(jsonResponse.members); - jsonResponse.members.should.have.length(1); - - jsonResponse.members[0].name.should.equal(member.name); - jsonResponse.members[0].email.should.equal(member.email); - jsonResponse.members[0].subscribed.should.equal(member.subscribed); - testUtils.API.isISO8601(jsonResponse.members[0].created_at).should.be.true(); - - should.exist(res.headers.location); - res.headers.location.should.equal(`http://127.0.0.1:2369${localUtils.API.getApiQuery('members/')}${res.body.members[0].id}/`); + .expectStatus(201) + .matchBodySnapshot({ + members: [memberMatcherNoIncludes] + }) + .matchHeaderSnapshot({ + etag: anyEtag, + location: anyString + }); mockManager.assert.sentEmail({ subject: '🙌 Complete your sign up to Ghost!', to: 'member_getting_confirmation@test.com' }); + // @TODO: do we really need to delete this member here? await agent - .delete(`members/${jsonResponse.members[0].id}/`) - .expectHeader('Cache-Control', testUtils.cacheRules.private) + .delete(`members/${body.members[0].id}/`) + .matchHeaderSnapshot({ + etag: anyEtag + }) .expectStatus(204); }); it('Can order by email_open_rate', async function () { await agent .get('members/?order=email_open_rate%20desc') - .expectHeader('Content-Type', /json/) - .expectHeader('Cache-Control', testUtils.cacheRules.private) .expectStatus(200) - .then((res) => { - should.not.exist(res.headers['x-cache-invalidate']); - const jsonResponse = res.body; - should.exist(jsonResponse.members); - localUtils.API.checkResponse(jsonResponse, 'members'); - jsonResponse.members.should.have.length(8); - - jsonResponse.members[0].email.should.equal('paid@test.com'); - jsonResponse.members[0].email_open_rate.should.equal(80); - jsonResponse.members[1].email.should.equal('member2@test.com'); - jsonResponse.members[1].email_open_rate.should.equal(50); - jsonResponse.members[2].email.should.equal('member1@test.com'); - should.equal(null, jsonResponse.members[2].email_open_rate); - jsonResponse.members[3].email.should.equal('trialing@test.com'); - should.equal(null, jsonResponse.members[3].email_open_rate); + .matchHeaderSnapshot({ + etag: anyEtag + }) + .matchBodySnapshot({ + members: new Array(8).fill(memberMatcherShallowIncludes) + }) + .expect(({body}) => { + const {members} = body; + assert.equal(members[0].email_open_rate > members[1].email_open_rate, true, 'Expected the first member to have a greater open rate than the second.'); }); await agent .get('members/?order=email_open_rate%20asc') - .expectHeader('Content-Type', /json/) - .expectHeader('Cache-Control', testUtils.cacheRules.private) .expectStatus(200) - .then((res) => { - const jsonResponse = res.body; - localUtils.API.checkResponse(jsonResponse, 'members'); - jsonResponse.members.should.have.length(8); - - jsonResponse.members[0].email.should.equal('member2@test.com'); - jsonResponse.members[0].email_open_rate.should.equal(50); - jsonResponse.members[1].email.should.equal('paid@test.com'); - jsonResponse.members[1].email_open_rate.should.equal(80); - jsonResponse.members[2].email.should.equal('member1@test.com'); - should.equal(null, jsonResponse.members[2].email_open_rate); - jsonResponse.members[3].email.should.equal('trialing@test.com'); - should.equal(null, jsonResponse.members[3].email_open_rate); + .matchHeaderSnapshot({ + etag: anyEtag + }) + .matchBodySnapshot({ + members: new Array(8).fill(memberMatcherShallowIncludes) + }) + .expect(({body}) => { + const {members} = body; + assert.equal(members[0].email_open_rate < members[1].email_open_rate, true, 'Expected the first member to have a smaller open rate than the second.'); }); }); - it('Can search by case-insensitive name', function () { - return agent + it('Sarch by case-insensitive name egg receives member with name Mr Egg', async function () { + await agent .get('members/?search=egg') - .expectHeader('Content-Type', /json/) - .expectHeader('Cache-Control', testUtils.cacheRules.private) .expectStatus(200) - .then((res) => { - should.not.exist(res.headers['x-cache-invalidate']); - const jsonResponse = res.body; - should.exist(jsonResponse); - should.exist(jsonResponse.members); - jsonResponse.members.should.have.length(1); - jsonResponse.members[0].email.should.equal('member1@test.com'); - localUtils.API.checkResponse(jsonResponse, 'members'); - localUtils.API.checkResponse(jsonResponse.members[0], 'member', 'subscriptions'); - localUtils.API.checkResponse(jsonResponse.meta.pagination, 'pagination'); + .matchBodySnapshot({ + members: [memberMatcherShallowIncludes] + }) + .matchHeaderSnapshot({ + etag: anyEtag }); }); - it('Can search by case-insensitive email', function () { - return agent + it('Search by case-insensitive email MEMBER2 receives member with email member2@test.com', async function () { + await agent .get('members/?search=MEMBER2') - .expectHeader('Content-Type', /json/) - .expectHeader('Cache-Control', testUtils.cacheRules.private) .expectStatus(200) - .then((res) => { - should.not.exist(res.headers['x-cache-invalidate']); - const jsonResponse = res.body; - should.exist(jsonResponse); - should.exist(jsonResponse.members); - jsonResponse.members.should.have.length(1); - jsonResponse.members[0].email.should.equal('member2@test.com'); - localUtils.API.checkResponse(jsonResponse, 'members'); - localUtils.API.checkResponse(jsonResponse.members[0], 'member', 'subscriptions'); - localUtils.API.checkResponse(jsonResponse.meta.pagination, 'pagination'); + .matchBodySnapshot({ + members: [memberMatcherShallowIncludes] + }) + .matchHeaderSnapshot({ + etag: anyEtag }); }); - it('Can search for paid members', function () { - return agent + it('Sarch for paid members retrieves member with email paid@test.com', async function () { + await agent .get('members/?search=egon&paid=true') - .expectHeader('Content-Type', /json/) - .expectHeader('Cache-Control', testUtils.cacheRules.private) .expectStatus(200) - .then((res) => { - should.not.exist(res.headers['x-cache-invalidate']); - const jsonResponse = res.body; - should.exist(jsonResponse); - should.exist(jsonResponse.members); - jsonResponse.members.should.have.length(1); - jsonResponse.members[0].email.should.equal('paid@test.com'); - localUtils.API.checkResponse(jsonResponse, 'members'); - localUtils.API.checkResponse(jsonResponse.members[0], 'member', 'subscriptions'); - localUtils.API.checkResponse(jsonResponse.meta.pagination, 'pagination'); + .matchBodySnapshot({ + members: [memberMatcherShallowIncludes] + }) + .matchHeaderSnapshot({ + etag: anyEtag }); }); - it('Search for non existing member returns empty result set', function () { - return agent + it('Search for non existing member returns empty result set', async function () { + await agent .get('members/?search=do_not_exist') - .expectHeader('Content-Type', /json/) - .expectHeader('Cache-Control', testUtils.cacheRules.private) .expectStatus(200) - .then((res) => { - should.not.exist(res.headers['x-cache-invalidate']); - const jsonResponse = res.body; - should.exist(jsonResponse); - should.exist(jsonResponse.members); - jsonResponse.members.should.have.length(0); + .matchHeaderSnapshot({ + etag: anyEtag + }) + .matchBodySnapshot({ + members: [] }); }); - it('Paid members subscriptions has price data', function () { + it('Can update a member with subscription included, change name to "Updated name"', async function () { const memberChanged = { name: 'Updated name' }; - return agent - .get('members/?search=egon&paid=true') - .expectHeader('Content-Type', /json/) - .expectHeader('Cache-Control', testUtils.cacheRules.private) + + const paidMember = fixtureManager.get('members', 2); + + await agent + .put(`members/${paidMember.id}/`) + .body({members: [memberChanged]}) .expectStatus(200) - .then((res) => { - should.not.exist(res.headers['x-cache-invalidate']); - const jsonResponse = res.body; - should.exist(jsonResponse); - should.exist(jsonResponse.members); - jsonResponse.members.should.have.length(1); - should.exist(jsonResponse.members[0].subscriptions[0].price); - return jsonResponse.members[0]; - }).then((paidMember) => { - return agent - .put(`members/${paidMember.id}/`) - .body({members: [memberChanged]}) - .expectHeader('Content-Type', /json/) - .expectHeader('Cache-Control', testUtils.cacheRules.private) - .expectStatus(200) - .then((res) => { - should.not.exist(res.headers['x-cache-invalidate']); - - const jsonResponse = res.body; - - should.exist(jsonResponse); - should.exist(jsonResponse.members); - jsonResponse.members.should.have.length(1); - localUtils.API.checkResponse(jsonResponse.members[0], 'member', ['subscriptions', 'products']); - should.exist(jsonResponse.members[0].subscriptions[0].price); - jsonResponse.members[0].name.should.equal(memberChanged.name); - }); + .matchHeaderSnapshot({ + etag: anyEtag + }) + .matchBodySnapshot({ + members: [memberMatcherShallowIncludes] }); }); - it('Add should fail when passing incorrect email_type query parameter', function () { - const member = { + it('Add should fail when passing incorrect email_type query parameter', async function () { + const newMember = { name: 'test', email: 'memberTestAdd@test.com' }; - return agent + await agent .post(`members/?send_email=true&email_type=lel`) - .body({members: [member]}) - .expectHeader('Content-Type', /json/) - .expectHeader('Cache-Control', testUtils.cacheRules.private) - .expectStatus(422); + .body({members: [newMember]}) + .expectStatus(422) + .matchHeaderSnapshot({ + etag: anyEtag + }) + .matchBodySnapshot({ + errors: [{ + id: anyErrorId + }] + }); }); - it('Add should fail when comped flag is passed in but Stripe is not enabled', function () { - const member = { + it('Add should fail when comped flag is passed in but Stripe is not enabled', async function () { + const newMember = { email: 'memberTestAdd@test.com', comped: true }; - return agent + await agent .post(`members/`) - .body({members: [member]}) - .expectHeader('Content-Type', /json/) - .expectHeader('Cache-Control', testUtils.cacheRules.private) + .body({members: [newMember]}) .expectStatus(422) - .then((res) => { - const jsonResponse = res.body; - - should.exist(jsonResponse); - should.exist(jsonResponse.errors); - - jsonResponse.errors[0].message.should.eql('Validation error, cannot save member.'); - jsonResponse.errors[0].context.should.match(/Missing Stripe connection./); + .matchHeaderSnapshot({ + etag: anyEtag + }) + .matchBodySnapshot({ + errors: [{ + id: anyErrorId + }] }); }); it('Can delete a member without cancelling Stripe Subscription', async function () { - const member = { + const newMember = { name: 'Member 2 Delete', email: 'Member2Delete@test.com' }; + // @TODO: use a fixture member in the right state using fixtureManager.get('members', x) + // @TODO: instead of creating a new member as this wastes a request const createdMember = await agent .post(`members/`) - .body({members: [member]}) - .expectHeader('Content-Type', /json/) - .expectHeader('Cache-Control', testUtils.cacheRules.private) + .body({members: [newMember]}) .expectStatus(201) - .then((res) => { - should.not.exist(res.headers['x-cache-invalidate']); - const jsonResponse = res.body; - should.exist(jsonResponse); - should.exist(jsonResponse.members); - jsonResponse.members.should.have.length(1); - - return jsonResponse.members[0]; + .matchHeaderSnapshot({ + etag: anyEtag, + location: anyString + }) + .matchBodySnapshot({ + members: [ + memberMatcherNoIncludes + ] + }) + .then(({body}) => { + return body.members[0]; }); await agent .delete(`members/${createdMember.id}/`) - .expectHeader('Cache-Control', testUtils.cacheRules.private) .expectStatus(204) - .then((res) => { - should.not.exist(res.headers['x-cache-invalidate']); + .matchHeaderSnapshot({ + etag: anyEtag + }) + .matchBodySnapshot(); + // @TODO: assert side effect - stripe subscription is not cancelled + }); - const jsonResponse = res.body; - - should.exist(jsonResponse); + it('Errors when fetching stats with unknown days param value', async function () { + await agent + .get('members/stats/?days=nope') + .expectStatus(422) + .matchHeaderSnapshot({ + etag: anyEtag + }) + .matchBodySnapshot({ + errors: [{ + id: anyErrorId + }] }); }); - - it('Errors when fetching stats with unknown days param value', function () { - return agent - .get('members/stats/?days=nope') - .expectHeader('Content-Type', /json/) - .expectHeader('Cache-Control', testUtils.cacheRules.private) - .expectStatus(422); - }); }); diff --git a/test/utils/e2e-framework.js b/test/utils/e2e-framework.js index ecde70aa65..2bed77be43 100644 --- a/test/utils/e2e-framework.js +++ b/test/utils/e2e-framework.js @@ -151,6 +151,7 @@ module.exports = { }, matchers: { anyString: any(String), + anyArray: any(Array), anyDate: stringMatching(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.000Z/), anyEtag: stringMatching(/(?:W\/)?"(?:[ !#-\x7E\x80-\xFF]*|\r\n[\t ]|\\.)*"/), anyObjectId: stringMatching(/[a-f0-9]{24}/), diff --git a/yarn.lock b/yarn.lock index 3d2fdfa60a..a2e5abd9f3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -926,7 +926,15 @@ debug "^4.1.0" globals "^11.1.0" -"@babel/types@^7.0.0", "@babel/types@^7.16.7", "@babel/types@^7.17.0", "@babel/types@^7.3.0": +"@babel/types@^7.0.0": + version "7.16.8" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.16.8.tgz#0ba5da91dd71e0a4e7781a30f22770831062e3c1" + integrity sha512-smN2DQc5s4M7fntyjGtyIPbRJv6wW4rU/94fmYJ7PKQuZkC0qGMHXJbg6sNGt12JmVr4k5YaptI/XtiLJBnmIg== + dependencies: + "@babel/helper-validator-identifier" "^7.16.7" + to-fast-properties "^2.0.0" + +"@babel/types@^7.16.7", "@babel/types@^7.17.0", "@babel/types@^7.3.0": version "7.17.0" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.17.0.tgz#a826e368bccb6b3d84acd76acad5c0d87342390b" integrity sha512-TmKSNO4D5rzhL5bjWFcVHHLETzfQ/AmbKpKPOSjlP0WoHZ6L911fgoOKY4Alp/emzG4cHJdyN49zpgkbXFEHHw==