diff --git a/.env.example b/.env.example
index 883c41856..9564b79b6 100644
--- a/.env.example
+++ b/.env.example
@@ -52,4 +52,14 @@ TEXTER_SIDEBOXES=celebration-gif,default-dynamicassignment,default-releasecontac
OWNER_CONFIGURABLE=ALL
NGP_VAN_API_KEY=
NGP_VAN_APP_NAME=
-NGP_VAN_WEBHOOK_BASE_URL=
\ No newline at end of file
+NGP_VAN_WEBHOOK_BASE_URL=
+NGP_VAN_API_KEY_ENCRYPTED=
+NGP_VAN_API_BASE_URL=
+NGP_VAN_CACHE_TTL=
+NGP_VAN_EXPORT_JOB_TYPE_ID=
+NGP_VAN_MAXIMUM_LIST_SIZE=
+NGP_VAN_CAUTIOUS_CELL_PHONE_SELECTION=
+ACTION_HANDLERS=
+MESSAGE_HANDLERS=
+CONTACT_LOADERS=
+SERVICE_MANAGERS=
\ No newline at end of file
diff --git a/README.md b/README.md
index 34da8a9bc..456f27d40 100644
--- a/README.md
+++ b/README.md
@@ -7,7 +7,7 @@ Spoke is an open source text-distribution tool for organizations to mobilize sup
Spoke was created by Saikat Chakrabarti and Sheena Pakanati, and is now maintained by MoveOn.org.
-The latest version is [13.0.1](https://github.com/MoveOnOrg/Spoke/tree/13.0.1) (see [release notes](https://github.com/MoveOnOrg/Spoke/blob/main/docs/RELEASE_NOTES.md#v1301))
+The latest version is [13.1.0](https://github.com/MoveOnOrg/Spoke/tree/13.1.0) (see [release notes](https://github.com/MoveOnOrg/Spoke/blob/main/docs/RELEASE_NOTES.md#v1310))
## Setting up Spoke
@@ -24,7 +24,7 @@ Want to know more?
### Quick Start with Heroku
This version of Spoke suitable for testing and, potentially, for small campaigns. This won't cost any money and will not support production(aka large-scale) usage. It's a great way to practice deploying Spoke or see it in action.
-
+
diff --git a/__test__/components/AssignmentSummary.test.js b/__test__/components/AssignmentSummary.test.js
index f12345339..1d806e6a2 100644
--- a/__test__/components/AssignmentSummary.test.js
+++ b/__test__/components/AssignmentSummary.test.js
@@ -22,6 +22,9 @@ function getAssignment({ isDynamic = false, counts = {} }) {
id: "1",
title: "New Campaign",
description: "asdf",
+ organization: {
+ allowSendAll: window.ALLOW_SEND_ALL
+ },
useDynamicAssignment: isDynamic,
hasUnassignedContacts: false,
introHtml: "yoyo",
@@ -79,7 +82,7 @@ describe("AssignmentSummary text", function t() {
);
});
-describe("AssignmentSummary actions inUSA and NOT AllowSendAll", () => {
+describe("AssignmentSummary actions when NOT AllowSendAll", () => {
function create(
unmessaged,
unreplied,
@@ -88,7 +91,6 @@ describe("AssignmentSummary actions inUSA and NOT AllowSendAll", () => {
skipped,
isDynamic
) {
- window.NOT_IN_USA = 0;
window.ALLOW_SEND_ALL = false;
return mount(
@@ -185,7 +187,7 @@ describe("AssignmentSummary actions inUSA and NOT AllowSendAll", () => {
});
});
-describe("AssignmentSummary NOT inUSA and AllowSendAll", () => {
+describe("AssignmentSummary when AllowSendAll", () => {
function create(
unmessaged,
unreplied,
@@ -194,7 +196,6 @@ describe("AssignmentSummary NOT inUSA and AllowSendAll", () => {
skipped,
isDynamic
) {
- window.NOT_IN_USA = 1;
window.ALLOW_SEND_ALL = true;
return mount(
{
).toBe("Send messages");
});
- it('renders "Send messages" with unreplied', () => {
+ it('renders "Respond" with unreplied', () => {
const actions = create(0, 1, 0, 0, 0, false);
expect(
actions
.find(Button)
.at(0)
.text()
- ).toBe("Send messages");
+ ).toBe("Respond");
});
});
@@ -328,17 +329,21 @@ describe("contacts filters", () => {
})}
/>
);
- const sendMessages = mockRender.mock.calls[0][0];
+ const respondMessages = mockRender.mock.calls[0][0];
+ expect(respondMessages.title).toBe("Respond");
+ expect(respondMessages.contactsFilter).toBe("reply");
+
+ const sendMessages = mockRender.mock.calls[1][0];
expect(sendMessages.title).toBe("Past Messages");
expect(sendMessages.contactsFilter).toBe("stale");
- const skippedMessages = mockRender.mock.calls[1][0];
+ const skippedMessages = mockRender.mock.calls[2][0];
expect(skippedMessages.title).toBe("Skipped Messages");
expect(skippedMessages.contactsFilter).toBe("skipped");
- const sendFirstTexts = mockRender.mock.calls[2][0];
+ const sendFirstTexts = mockRender.mock.calls[3][0];
expect(sendFirstTexts.title).toBe("Send messages");
- expect(sendFirstTexts.contactsFilter).toBe("all");
+ expect(sendFirstTexts.contactsFilter).toBe("text");
});
});
diff --git a/__test__/extensions/contact-loaders/ngpvan/ngpvan.test.js b/__test__/extensions/contact-loaders/ngpvan/ngpvan.test.js
index be96dab56..c108bf912 100644
--- a/__test__/extensions/contact-loaders/ngpvan/ngpvan.test.js
+++ b/__test__/extensions/contact-loaders/ngpvan/ngpvan.test.js
@@ -469,6 +469,7 @@ describe("ngpvan", () => {
["NGP_VAN_API_BASE_URL", organization],
["NGP_VAN_TIMEOUT", organization],
["NGP_VAN_APP_NAME", organization],
+ ["NGP_VAN_API_KEY_ENCRYPTED", organization, {}],
["NGP_VAN_API_KEY", organization],
["NGP_VAN_DATABASE_MODE", organization],
["NGP_VAN_EXPORT_JOB_TYPE_ID", organization],
diff --git a/__test__/extensions/contact-loaders/ngpvan/util.test.js b/__test__/extensions/contact-loaders/ngpvan/util.test.js
index d2d72fb13..261d4e803 100644
--- a/__test__/extensions/contact-loaders/ngpvan/util.test.js
+++ b/__test__/extensions/contact-loaders/ngpvan/util.test.js
@@ -120,12 +120,13 @@ describe("ngpvan/util", () => {
}
try {
- auth = Van.getAuth(organization);
+ auth = await Van.getAuth(organization);
} catch (caughtException) {
error = caughtException;
} finally {
expect(config.getConfig.mock.calls).toEqual([
["NGP_VAN_APP_NAME", organization],
+ ["NGP_VAN_API_KEY_ENCRYPTED", organization, {}],
["NGP_VAN_API_KEY", organization],
["NGP_VAN_DATABASE_MODE", organization]
]);
diff --git a/__test__/extensions/service-vendors/twilio.test.js b/__test__/extensions/service-vendors/twilio.test.js
index 7d4485f67..cea2f4079 100644
--- a/__test__/extensions/service-vendors/twilio.test.js
+++ b/__test__/extensions/service-vendors/twilio.test.js
@@ -849,7 +849,6 @@ describe("config functions", () => {
["TWILIO_ACCOUNT_SID", organization, expectedConfigOpts]
]);
expect(configFunctions.getConfig.mock.calls).toEqual([
- ["TWILIO_AUTH_TOKEN_ENCRYPTED", organization, expectedConfigOpts],
["TWILIO_AUTH_TOKEN_ENCRYPTED", organization, expectedConfigOpts],
["TWILIO_ACCOUNT_SID", organization, expectedConfigOpts],
["TWILIO_ACCOUNT_SID", organization, expectedConfigOpts],
diff --git a/__test__/server/api/campaign/campaign.test.js b/__test__/server/api/campaign/campaign.test.js
index 7dd6f5a7b..9884eabbb 100644
--- a/__test__/server/api/campaign/campaign.test.js
+++ b/__test__/server/api/campaign/campaign.test.js
@@ -863,7 +863,8 @@ describe("Bulk Send", () => {
) => {
process.env.ALLOW_SEND_ALL = params.allowSendAll;
process.env.ALLOW_SEND_ALL_ENABLED = params.allowSendAllEnabled;
- process.env.BULK_SEND_CHUNK_SIZE = params.bulkSendChunkSize;
+ process.env.BULK_SEND_BATCH_SIZE =
+ params.bulkSendBatchSize || params.bulkSendChunkSize;
testCampaign.use_dynamic_assignment = true;
await createScript(testAdminUser, testCampaign);
@@ -1014,6 +1015,20 @@ describe("Bulk Send", () => {
};
await testBulkSend(params, 0, expectErrorBulkSending);
});
+
+ it("should send initial texts to as many contacts as are in the batch size if batch size is smaller than the chunk size", async () => {
+ const params = {
+ allowSendAll: true,
+ allowSendAllEnabled: true,
+ bulkSendBatchSize: Math.round(NUMBER_OF_CONTACTS / 4),
+ bulkSendChunkSize: NUMBER_OF_CONTACTS
+ };
+ await testBulkSend(
+ params,
+ params.bulkSendBatchSize,
+ expectSuccessBulkSending(params.bulkSendBatchSize)
+ );
+ });
});
describe("campaigns query", () => {
diff --git a/__test__/server/api/people.test.js b/__test__/server/api/people.test.js
index 3496f4209..beeb84700 100644
--- a/__test__/server/api/people.test.js
+++ b/__test__/server/api/people.test.js
@@ -1,7 +1,12 @@
+/**
+ * @jest-environment jsdom
+ */
/* eslint-disable no-unused-expressions, consistent-return */
import { r } from "../../../src/server/models/";
import { getUsersGql } from "../../../src/containers/PeopleList";
import { GraphQLError } from "graphql/error";
+import { resolvers } from "../../../src/server/api/schema";
+import { validate as uuidValidate } from 'uuid';
import {
setupTest,
@@ -479,4 +484,49 @@ describe("people", () => {
]);
});
});
+
+ describe("reset password", () => {
+ /**
+ * Run the resetUserPassword mutation
+ * @param {number} organizationId
+ * @param {number} texterId
+ * @param {number} userId
+ * @returns Promise
+ */
+ function resetUserPassword(admin, organizationId, texterId) {
+ return resolvers.RootMutation.resetUserPassword(null, {
+ organizationId: organizationId,
+ userId: texterId
+ }, {
+ loaders: {
+ organization: {
+ load: async id => {
+ return (await r.knex("organization").where({ id }))[0];
+ }
+ }
+ },
+ user: admin
+ });
+ }
+
+ it("reset local password", () => {
+ resetUserPassword(testAdminUsers[0], organizationId, testTexterUsers[0].id).then(uuid => {
+ // Non-Auth0 password reset will return verion 4 UUID
+ expect(uuidValidate(uuid)).toBeTruthy();
+ });
+ });
+
+ it("reset Auth0 password", () => {
+ // Remove PASSPORT_STRATEGY env var. PASSPORT_STRATEGY will default to "auth0" if there's nothing explicitly set
+ delete window.PASSPORT_STRATEGY;
+
+ resetUserPassword(testAdminUsers[0], organizationId, testTexterUsers[0].id).catch(e => {
+ // Auth0 password reset will attempt to make HTTP request, which will fail in Jest test
+ const match = e.message.match(/Error: Request id (.*) failed; all 2 retries exhausted/);
+
+ expect(match).toHaveLength(2);
+ expect(uuidValidate(match[1])).toBeTruthy();
+ });
+ });
+ });
});
diff --git a/app.json b/app.json
index 9495516b8..a52f7d49e 100644
--- a/app.json
+++ b/app.json
@@ -44,19 +44,18 @@
},
"DEFAULT_SERVICE": {
- "description": "specifies using twilio api ",
- "required": true,
- "value": "twilio"
+ "description": "type bandwidth or twilio",
+ "required": true
},
"TWILIO_ACCOUNT_SID": {
"description": "for twilio integration and connected to twilio account",
- "required": true
+ "required": false
},
"TWILIO_AUTH_TOKEN": {
"description": "for twilio integration and connected to twilio account",
- "required": true
+ "required": false
},
"TWILIO_MULTI_ORG": {
@@ -184,9 +183,9 @@
}
},
"addons": [
- "heroku-postgresql:hobby-dev",
+ "heroku-postgresql:basic",
{
- "plan": "heroku-redis:hobby-dev",
+ "plan": "heroku-redis:mini",
"options": {
"maxmemory-policy": "volatile-lru"
}
diff --git a/docs/HOWTO-configure-auth0.md b/docs/HOWTO-configure-auth0.md
index 6e01eb44d..381fed832 100644
--- a/docs/HOWTO-configure-auth0.md
+++ b/docs/HOWTO-configure-auth0.md
@@ -51,7 +51,7 @@ callback(null, user, context);
-
+