Repository: DEFRA/fcp-sfd-comms
Pull request #22: SFDP-436 - Allow Override of Simulator Email Validation
Author: login: sfitz42
Created: 2025-05-07T16:20:15Z | Merged: 2025-05-09T14:57:26Z

# Description

Confirm each of the checklist points has been completed as part of the review process.

## Checklist

- [ ] has cloned repo locally
- [ ] has successfully run service
- [ ] has verified all ACs covered by tests
- [ ] has verified PR branch deploys correctly
- [ ] have verified all tests pass
- [ ] have checked README has been updated
- [ ] has verified code is maintable
- [ ] has suggested refactoring opportunities or simplification
- [ ] has challenged complexity



Diff:
diff --git a/package.json b/package.json
index 45aa66e..bb32959 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "fcp-sfd-comms",
-  "version": "0.16.0",
+  "version": "0.18.0",
   "description": "Comms Service for Single Front Door",
   "main": "src/index.js",
   "type": "module",
diff --git a/src/config/notify.js b/src/config/notify.js
index 3934eef..a652ea0 100644
--- a/src/config/notify.js
+++ b/src/config/notify.js
@@ -51,6 +51,12 @@ export const notifyConfig = {
         default: 15,
         env: 'MESSAGE_RETRY_DELAY'
       }
+    },
+    allowSimulatorEmails: {
+      doc: 'Allow use of GOV Notify simulator emails.',
+      format: Boolean,
+      default: process.env.NODE_ENV === 'test' || process.env.NODE_ENV === 'development',
+      env: 'ALLOW_SIMULATOR_EMAILS'
     }
   }
 }
diff --git a/src/schemas/common/recipient.js b/src/schemas/common/recipient.js
index 573c9a6..330937c 100644
--- a/src/schemas/common/recipient.js
+++ b/src/schemas/common/recipient.js
@@ -1,16 +1,9 @@
 import Joi from 'joi'
 
-import environments from '../../constants/environments.js'
-
 import { config } from '../../config/index.js'
 
-const nonProductionEnvironments = [
-  environments.DEVELOPMENT,
-  environments.TEST
-]
-
 const createComponent = () => {
-  if (nonProductionEnvironments.includes(config.get('env'))) {
+  if (config.get('notify.allowSimulatorEmails')) {
     return Joi.alternatives().try(
       Joi.string().email(),
       Joi.string().valid('temp-fail@simulator.notify', 'perm-fail@simulator.notify')
diff --git a/test/unit/schemas/comms-request/v1.test.js b/test/unit/schemas/comms-request/v1.test.js
index 264d0d3..f3e01db 100644
--- a/test/unit/schemas/comms-request/v1.test.js
+++ b/test/unit/schemas/comms-request/v1.test.js
@@ -1,4 +1,4 @@
-import { afterAll, beforeEach, describe, expect, test, vi } from 'vitest'
+import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'
 
 import mockV1CommsRequest from '../../../mocks/comms-request/v1.js'
 import environments from '../../../../src/constants/environments.js'
@@ -372,6 +372,46 @@ describe('comms request schema v1 validation', () => {
         }))
       })
 
+      test('should allow temp fail simulator email in production environment if overridden', async () => {
+        process.env.NODE_ENV = environments.PRODUCTION
+        process.env.ALLOW_SIMULATOR_EMAILS = true
+
+        const { v1: mockedV1 } = await import('../../../../src/schemas/comms-request/index.js')
+
+        const message = {
+          ...mockV1Message,
+          data: {
+            ...mockV1Message.data,
+            recipient: 'temp-fail@simulator.notify'
+          }
+        }
+
+        const [value, error] = await validate(mockedV1, message)
+
+        expect(value).toBeTruthy()
+        expect(error).toBeNull()
+      })
+
+      test('should allow perm fail simulator email in production environment if overridden', async () => {
+        process.env.NODE_ENV = environments.PRODUCTION
+        process.env.ALLOW_SIMULATOR_EMAILS = true
+
+        const { v1: mockedV1 } = await import('../../../../src/schemas/comms-request/index.js')
+
+        const message = {
+          ...mockV1Message,
+          data: {
+            ...mockV1Message.data,
+            recipient: 'temp-fail@simulator.notify'
+          }
+        }
+
+        const [value, error] = await validate(mockedV1, message)
+
+        expect(value).toBeTruthy()
+        expect(error).toBeNull()
+      })
+
       test.each([
         environments.DEVELOPMENT,
         environments.TEST,
@@ -422,7 +462,7 @@ describe('comms request schema v1 validation', () => {
         }))
       })
 
-      test('should reject array of emails in production environment with string error', async (env) => {
+      test('should reject array of emails in production environment with string error', async () => {
         process.env.NODE_ENV = environments.PRODUCTION
 
         const { v1: mockedV1 } = await import('../../../../src/schemas/comms-request/index.js')
@@ -446,8 +486,10 @@ describe('comms request schema v1 validation', () => {
         }))
       })
 
-      afterAll(() => {
-        process.env = originalEnv
+      afterEach(() => {
+        process.env = {
+          ...originalEnv
+        }
       })
     })
   })


Reviews:
user: login: FayToward
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-09T13:48:38Z
user: login: sfitz42
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-09T13:57:05Z
user: login: FayToward
body: LGTM
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-09T14:37:47Z

Review comments:
diff_hunk: @@ -1,6 +1,6 @@
 {
   "name": "fcp-sfd-comms",
-  "version": "0.16.0",
+  "version": "0.18.0",
path: package.json
user: login: FayToward
body: is there a reason 0.17.0 has been skipped?
created_at: 2025-05-09T13:48:38Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,6 +1,6 @@
 {
   "name": "fcp-sfd-comms",
-  "version": "0.16.0",
+  "version": "0.18.0",
path: package.json
user: login: sfitz42
body: Main branch is currently at 0.17.0. Must've missed updating the version here at somepoint.
created_at: 2025-05-09T13:57:05Z
author_association: CONTRIBUTOR

PR comments:
user: login: sonarqubecloud[bot]
created_at: 2025-05-09T13:46:34Z
body: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=DEFRA_fcp-sfd-comms&pullRequest=22) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=DEFRA_fcp-sfd-comms&pullRequest=22&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=DEFRA_fcp-sfd-comms&pullRequest=22&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=DEFRA_fcp-sfd-comms&pullRequest=22&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [100.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=DEFRA_fcp-sfd-comms&pullRequest=22&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=DEFRA_fcp-sfd-comms&pullRequest=22&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=DEFRA_fcp-sfd-comms&pullRequest=22)


author_association: NONE