Skip to content

PUB-2905 Stop deletion of list types if there are remaining location subscription #1505

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Jul 11, 2025
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ export default class BulkUnsubscribeConfirmationController {
if (req.body['bulk-unsubscribe-choice'] === 'yes') {
const unsubscribeResponse = await subscriptionService.bulkDeleteSubscriptions(
subscriptionsToDelete,
req.user['userId']
req.user['userId'],
req.user['userProvenance']
);
unsubscribeResponse
? res.redirect(unsubscribeConfirmedUrl)
Expand Down
3 changes: 2 additions & 1 deletion src/main/controllers/UnsubscribeConfirmationController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ export default class UnsubscribeConfirmationController {
if (req.body?.['unsubscribe-confirm'] === 'yes') {
const unsubscribeResponse = await subscriptionService.unsubscribe(
req.body.subscription,
req.user['userId']
req.user['userId'],
req.user['userProvenance']
);
unsubscribeResponse
? res.render(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export default class ManageThirdPartyUsersSubscriptionsController {
) {
await thirdPartyService.handleThirdPartySubscriptionUpdate(
req.user['userId'],
req.user['userProvenance'],
selectedUser,
selectedListTypes,
selectedChannel
Expand Down
55 changes: 44 additions & 11 deletions src/main/service/SubscriptionService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,12 +142,46 @@ export class SubscriptionService {
};
}

public async unsubscribe(subscriptionId: string, userId: string): Promise<object> {
return subscriptionRequests.unsubscribe(subscriptionId, userId);
public async unsubscribe(subscriptionId: string, userId: string, userProvenance: string): Promise<boolean> {
const response = await subscriptionRequests.unsubscribe(subscriptionId, userId);
if (response) {
return await this.configureListTypeAfterUnsubscribe(userId, userProvenance);
}
return false;
}

public async bulkDeleteSubscriptions(subscriptionIds: string[], userId: string): Promise<object> {
return subscriptionRequests.bulkDeleteSubscriptions(subscriptionIds, userId);
public async bulkDeleteSubscriptions(
subscriptionIds: string[],
userId: string,
userProvenance: string
): Promise<boolean> {
const response = await subscriptionRequests.bulkDeleteSubscriptions(subscriptionIds, userId);
if (response) {
return await this.configureListTypeAfterUnsubscribe(userId, userProvenance);
}
return false;
}

private async configureListTypeAfterUnsubscribe(userId: string, userProvenance: string): Promise<boolean> {
const userSubscriptions = await this.getSubscriptionsByUser(userId);
const applicableListTypes = await this.generateAppropriateListTypes(userId, userProvenance, userSubscriptions);

if (userSubscriptions['locationSubscriptions'].length > 0) {
const storedListTypes = userSubscriptions['locationSubscriptions'][0]['listType'];
const languageToConfigure = userSubscriptions['locationSubscriptions'][0]['listLanguage'];
const listTypesToConfigure = [];
for (const [listName, listType] of applicableListTypes) {
if (listType.checked && storedListTypes.includes(listName)) {
listTypesToConfigure.push(listName);
}
}
return await this.configureListTypeForLocationSubscriptions(
userId,
listTypesToConfigure,
languageToConfigure
);
}
return true;
}

public async handleNewSubscription(pendingSubscription, user): Promise<void> {
Expand Down Expand Up @@ -426,7 +460,8 @@ export class SubscriptionService {
* @param language The language the application is in.
*/
public async generateListTypesForCourts(userId, userRole, language): Promise<object> {
const applicableListTypes = await this.generateAppropriateListTypes(userId, userRole);
const userSubscriptions = await this.getSubscriptionsByUser(userId);
const applicableListTypes = await this.generateAppropriateListTypes(userId, userRole, userSubscriptions);
return this.generateAlphabetisedListTypes(applicableListTypes, language);
}

Expand Down Expand Up @@ -474,9 +509,7 @@ export class SubscriptionService {
return selectedListLanguage;
}

private async generateAppropriateListTypes(userId, userRole): Promise<Map<string, ListType>> {
const userSubscriptions = await this.getSubscriptionsByUser(userId);

private async generateAppropriateListTypes(userId, userProvenance, userSubscriptions): Promise<Map<string, ListType>> {
const selectedListTypes = await this.getUserSubscriptionListType(userId);
const courtJurisdictionTypes = [];
for (const subscription of userSubscriptions['locationSubscriptions']) {
Expand All @@ -488,7 +521,7 @@ export class SubscriptionService {
}
}

return this.findApplicableListTypeForCourts(courtJurisdictionTypes, selectedListTypes, userRole);
return this.findApplicableListTypeForCourts(courtJurisdictionTypes, selectedListTypes, userProvenance);
}

public async generateListTypeForCourts(userRole, language, userId): Promise<object> {
Expand All @@ -508,7 +541,7 @@ export class SubscriptionService {
private findApplicableListTypeForCourts(
courtJurisdictionTypes,
selectedListTypes,
userRole
userProvenance
): Map<string, ListType> {
const listTypes = publicationService.getListTypes();
const sortedListTypes = new Map(
Expand All @@ -519,7 +552,7 @@ export class SubscriptionService {
for (const [listName, listType] of sortedListTypes) {
if (
listType.jurisdictionTypes.some(value => courtJurisdictionTypes.includes(value)) &&
(listType.restrictedProvenances.length === 0 || listType.restrictedProvenances.includes(userRole))
(listType.restrictedProvenances.length === 0 || listType.restrictedProvenances.includes(userProvenance))
) {
listType.checked = selectedListTypes != null && selectedListTypes.includes(listName);
applicableListTypes.set(listName, listType);
Expand Down
10 changes: 8 additions & 2 deletions src/main/service/ThirdPartyService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,13 @@ export class ThirdPartyService {
* @param selectedListTypes The list types that have been selected.
* @param selectedChannel The channel that has been selected.
*/
public async handleThirdPartySubscriptionUpdate(adminUserId, selectedUser, selectedListTypes, selectedChannel) {
public async handleThirdPartySubscriptionUpdate(
adminUserId,
userProvenance,
selectedUser,
selectedListTypes,
selectedChannel
) {
selectedListTypes = selectedListTypes || [];
selectedListTypes = Array.isArray(selectedListTypes) ? selectedListTypes : Array.of(selectedListTypes);

Expand All @@ -86,7 +92,7 @@ export class ThirdPartyService {
'Unsubscribing ' + selectedUser + ' for list type ' + sub.listType + ' by admin ' + adminUserId
);

this.subscriptionService.unsubscribe(sub.subscriptionId, adminUserId);
this.subscriptionService.unsubscribe(sub.subscriptionId, adminUserId, userProvenance);
} else {
if (sub.channel !== selectedChannel) {
this.logger.info(
Expand Down
31 changes: 20 additions & 11 deletions src/test/end-to-end/tests/verified/email-subscriptions-test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import { DateTime } from 'luxon';
import { createLocation, createTestUserAccount, uploadPublication } from '../../shared/testingSupportApi';
import {
createLocation,
createSubscription,
createTestUserAccount,
uploadPublication,
} from '../../shared/testingSupportApi';
import { randomData } from '../../shared/random-data';
import { config as testConfig, config } from '../../../config';

Expand Down Expand Up @@ -598,11 +603,14 @@ Scenario('I as a verified user should be able to filter locations while subscrib
Scenario('I as a verified user should be able to filter and select which list type to receive', async ({ I }) => {
const locationId = randomData.getRandomLocationId();
const locationName = config.TEST_SUITE_PREFIX + randomData.getRandomString();

const locationIdOne = randomData.getRandomLocationId();
const locationNameOne = config.TEST_SUITE_PREFIX + randomData.getRandomString();
const testUserEmail = randomData.getRandomEmailAddress();

await createLocation(locationId, locationName);
const testUser = await createTestUserAccount(TEST_FIRST_NAME, TEST_LAST_NAME, testUserEmail);
await createLocation(locationIdOne, locationNameOne);
await createLocation(locationId, locationName);
await createSubscription(locationIdOne, locationNameOne, testUser['userId'] as string);

I.loginTestMediaUser(testUser['email'], secret(testConfig.TEST_USER_PASSWORD));
I.waitForText('Your account');
Expand All @@ -619,12 +627,6 @@ Scenario('I as a verified user should be able to filter and select which list ty
I.see(locationName);
I.click('Continue');
I.waitForText('Select List Types');
I.see(
'Choose the lists you will receive for your selected courts and tribunals. This will not affect any ' +
"specific cases you may have subscribed to. Also don't forget to come" +
' back regularly to see new list types as we add more.'
);

I.see('Civil Daily Cause List');
I.see('Civil and Family Daily Cause List');
I.see('Family Daily Cause List');
Expand All @@ -635,14 +637,12 @@ Scenario('I as a verified user should be able to filter and select which list ty
I.see('Single Justice Procedure Press List');
I.see('Employment Tribunals Daily List');
I.see('Employment Tribunals Fortnightly Press List');

I.dontSee('Crown Daily List');
I.dontSee('Criminal Injuries Compensation');
I.dontSee('Care Standards Tribunal');
I.dontSee('Primary Health Tribunal');
I.dontSee('First-tier Tribunal');
I.dontSee('Upper Tribunal');

I.checkOption('#CIVIL_AND_FAMILY_DAILY_CAUSE_LIST');
I.click('Continue');
I.waitForText('What version of the list do you want to receive?');
Expand All @@ -668,8 +668,17 @@ Scenario('I as a verified user should be able to filter and select which list ty
I.waitForText('List types updated');

I.click('manage your current email subscriptions');
I.waitForText('Your email subscriptions');
I.click(locate('//tr').withText(locationNameOne).find('a').withText('Unsubscribe'));
I.waitForText('Are you sure you want to remove this subscription?');
I.click('#unsubscribe-confirm');
I.click('Continue');
I.waitForText('Your subscription has been removed');
I.click('Email subscriptions');

I.click('Edit list types');
I.waitForText('Select List Types');
I.seeCheckboxIsChecked('#CIVIL_DAILY_CAUSE_LIST');
I.dontSeeCheckboxIsChecked('#CIVIL_AND_FAMILY_DAILY_CAUSE_LIST');
I.click('Email subscriptions');
I.click(locate('//tr').withText(locationName).find('a').withText('Unsubscribe'));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ describe('Bulk Unsubscribe Confirmation Controller', () => {
.resolves({ ...caseSubscriptions, ...locationSubscriptions });

stub.withArgs(['aaa', 'bbb']).resolves(true);
stub.withArgs(['foo']).resolves(undefined);
stub.withArgs(['foo']).resolves(false);

it("should render the bulk unsubscribe confirmed page if 'Yes' is selected", () => {
const responseMock = sinon.mock(response);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const unsubscribeConfirmationController = new UnsubscribeConfirmationController(
describe('Unsubscribe Confirmation Controller', () => {
beforeEach(() => {
stub.withArgs('123').resolves(true);
stub.withArgs('foo').resolves(undefined);
stub.withArgs('foo').resolves(false);
});

const i18n = {
Expand Down
Loading
Loading