Repository: CDCgov/data-exchange-processing-status
Pull request #373: PHDO 751 - Tests for subscribeDeadlineCheck
Author: login: cyber-decker
Created: 2025-05-13T18:51:59Z | Merged: 2025-05-22T15:07:36Z

# Deadline Check Subscription System Tests
## Test Coverage
- Email Notification Tests
  - Tests for duration-based cron schedules (@every 10s)
  - Tests for classic cron schedules (* * * * *)
  - Tests for multiple email recipients
  - Tests for specific data stream subscriptions
- Webhook Notification Tests
  - Tests for duration-based cron schedules (@every 5s)
  - Tests for classic cron schedules (* * * * *)
  - Tests for webhook token authentication
  - Tests for webhook request validation

 ## Test Infrastructure

- Uses Playwright for test execution
- Integrates with Mailhog for email testing
- Integrates with webhook service for webhook testing
- Implements proper test cleanup (subscription removal)
- Includes snapshot testing for error cases
 
## Error Case Testing

- Invalid cron schedules
- Invalid notification types
- Invalid email formats
- Invalid webhook URLs
- Invalid deadline values


Diff:
diff --git a/test/playwright/fixtures/dataGenerator.ts b/test/playwright/fixtures/dataGenerator.ts
index 791f0a64..9dbe3c87 100644
--- a/test/playwright/fixtures/dataGenerator.ts
+++ b/test/playwright/fixtures/dataGenerator.ts
@@ -1,5 +1,5 @@
 import { faker } from "@faker-js/faker"
-import { NotificationType, WorkflowSubscriptionForDataStreamsInput} from '@gql';
+import { NotificationType, WorkflowSubscriptionDeadlineCheckInput, WorkflowSubscriptionForDataStreamsInput} from '@gql';
 
 export type UploadReport = {
     report_schema_version: string,
@@ -242,16 +242,8 @@ export function createSubscriptionInput({
     notificationType = NotificationType.Email,
     webhookUrl = "",
     sinceDays = 1
-}: {
-    emailAddresses?: string[];
-    cronSchedule?: string;
-    dataStreamIds?: string[];
-    dataStreamRoutes?: string[];
-    jurisdictions?: string[];
-    notificationType?: NotificationType;
-    webhookUrl?: string;
-    sinceDays?: number;
-}): WorkflowSubscriptionForDataStreamsInput {
+}: Partial<WorkflowSubscriptionForDataStreamsInput>
+): WorkflowSubscriptionForDataStreamsInput {
     return {
         cronSchedule,
         dataStreamIds,
@@ -264,6 +256,29 @@ export function createSubscriptionInput({
     };
 }
 
+export function createDeadlineSubscriptionInput({
+    emailAddresses = [],
+    cronSchedule = "0 0 1 12 *",
+    dataStreamId = "",
+    dataStreamRoute = "",
+    deadlineTime = "06:00:00",
+    expectedJurisdictions = [],
+    notificationType = NotificationType.Email,
+    webhookUrl = "",
+}: Partial<WorkflowSubscriptionDeadlineCheckInput>
+): WorkflowSubscriptionDeadlineCheckInput {  
+    return {
+        emailAddresses,
+        webhookUrl,
+        cronSchedule,
+        dataStreamId,
+        dataStreamRoute,
+        expectedJurisdictions,
+        deadlineTime,
+        notificationType,
+    };
+}
+
 const dataGenerator = {
     addSeconds,
     createMinimalReport,
diff --git a/test/playwright/tests/__snapshot__/subscribeDeadlineCheck.spec.ts/GraphQL-subscribeDeadlineCheck-subscribing-errors-invalid-chron-schedule-invalid-cron b/test/playwright/tests/__snapshot__/subscribeDeadlineCheck.spec.ts/GraphQL-subscribeDeadlineCheck-subscribing-errors-invalid-chron-schedule-invalid-cron
new file mode 100644
index 00000000..e8663ac5
--- /dev/null
+++ b/test/playwright/tests/__snapshot__/subscribeDeadlineCheck.spec.ts/GraphQL-subscribeDeadlineCheck-subscribing-errors-invalid-chron-schedule-invalid-cron
@@ -0,0 +1 @@
+[{"message":"INVALID_ARGUMENT: invalid CronSchedule.","locations":[],"path":["subscribeDeadlineCheck"],"extensions":{"classification":"BadRequestException"}}]
\ No newline at end of file
diff --git a/test/playwright/tests/__snapshot__/subscribeDeadlineCheck.spec.ts/GraphQL-subscribeDeadlineCheck-subscribing-errors-invalid-deadline-value-invalid-notification-type b/test/playwright/tests/__snapshot__/subscribeDeadlineCheck.spec.ts/GraphQL-subscribeDeadlineCheck-subscribing-errors-invalid-deadline-value-invalid-notification-type
new file mode 100644
index 00000000..c1af5a75
--- /dev/null
+++ b/test/playwright/tests/__snapshot__/subscribeDeadlineCheck.spec.ts/GraphQL-subscribeDeadlineCheck-subscribing-errors-invalid-deadline-value-invalid-notification-type
@@ -0,0 +1 @@
+[{"message":"Variable 'subscription' has an invalid value: Invalid local time value : 'INVALID'. because of : 'Text 'INVALID' could not be parsed at index 0'","locations":[{"line":1,"column":33}]}]
\ No newline at end of file
diff --git a/test/playwright/tests/__snapshot__/subscribeDeadlineCheck.spec.ts/GraphQL-subscribeDeadlineCheck-subscribing-errors-invalid-notification-type-invalid-notification-type b/test/playwright/tests/__snapshot__/subscribeDeadlineCheck.spec.ts/GraphQL-subscribeDeadlineCheck-subscribing-errors-invalid-notification-type-invalid-notification-type
new file mode 100644
index 00000000..b0afab46
--- /dev/null
+++ b/test/playwright/tests/__snapshot__/subscribeDeadlineCheck.spec.ts/GraphQL-subscribeDeadlineCheck-subscribing-errors-invalid-notification-type-invalid-notification-type
@@ -0,0 +1 @@
+[{"message":"Variable 'subscription' has an invalid value: Invalid input for enum 'NotificationType'. No value found for name 'INVALID'","locations":[{"line":1,"column":33}]}]
\ No newline at end of file
diff --git a/test/playwright/tests/subscribeDeadlineCheck.spec.ts b/test/playwright/tests/subscribeDeadlineCheck.spec.ts
new file mode 100644
index 00000000..0b53e468
--- /dev/null
+++ b/test/playwright/tests/subscribeDeadlineCheck.spec.ts
@@ -0,0 +1,293 @@
+import { test, expect } from '@fixtures/gql';
+import { NotificationType } from '@gql';
+import { GraphQLError } from 'graphql';
+import { createDeadlineSubscriptionInput } from '../fixtures/dataGenerator';
+
+const EMAIL_SERVICE = process.env.EMAILURL || "http://localhost:8025";
+const WEBHOOK_SERVICE = process.env.WEBHOOKURL || "http://webhook:80";
+const WEBHOOK_SERVICE_UI = process.env.WEBHOOKAPI || "http://localhost:8084";
+
+type GraphQLErrorResponse = { errors: GraphQLError[] };
+
+let subscriptions:string[] = []
+
+test.describe('GraphQL subscribeDeadlineCheck', () => {
+    test.setTimeout(90000); 
+
+    test.beforeAll(async ({ request }) => { 
+        const mailhogResponse = await request.delete(`${EMAIL_SERVICE}/api/v1/messages`);
+        expect(mailhogResponse.status()).toBe(200);
+    });
+
+    test.afterEach(async ({ gql }) => { 
+        subscriptions.forEach(async (subscriptionId) => {
+            const response = await gql.unsubscribeNotificationWorkflow({ subscriptionId: subscriptionId });
+            expect(response.unsubscribeNotificationWorkflow.subscriptionId).toBe(subscriptionId);
+        });
+        subscriptions = [];
+    });
+
+    test('subscribing via email with duration cron', async ({ gql, request }) => {
+        const subscriptionEmail = `subscribeDeadlineCheck-cron-duration@test.com`;
+        const subscription = createDeadlineSubscriptionInput({
+            emailAddresses: [subscriptionEmail],
+            cronSchedule: "@every 10s"
+        });
+
+        const res = await gql.subscribeDeadlineCheck({ subscription });
+        expect(res.subscribeDeadlineCheck).toBeDefined();
+        expect(res.subscribeDeadlineCheck.subscriptionId).toBeDefined();
+        
+        const subscriptionId = res.subscribeDeadlineCheck.subscriptionId!.toString();
+        subscriptions.push(subscriptionId);
+
+        await expect.poll(async () => {
+            const mailhogResponse = await request.get(`${EMAIL_SERVICE}/api/v2/search?kind=containing&query=` + subscriptionEmail);
+            const emails = await mailhogResponse.json();
+            return emails.total;
+        }, {
+            message: 'Email should be found',
+            timeout: 60000,
+        }).toBeGreaterThan(0);
+
+        const mailhogResponse = await request.get(`${EMAIL_SERVICE}/api/v2/search?kind=containing&query=` + subscriptionEmail);
+        const emails = await mailhogResponse.json();
+        expect(emails.items[0].Content.Headers.To[0]).toBe(subscriptionEmail);
+        expect(emails.items[0].Content.Headers.Subject[0]).toContain(`PHDO DEADLINE MISSED NOTIFICATION`);
+    });
+
+    test('subscribing via email with classic cron', async ({ gql, request }) => {        
+        const subscriptionEmail = `subscribeDeadlineCheck-cron-classic@test.com`;
+        const subscription = createDeadlineSubscriptionInput({
+            emailAddresses: [subscriptionEmail],
+            cronSchedule: "* * * * *"
+        });
+
+        const res = await gql.subscribeDeadlineCheck({ subscription });
+        expect(res.subscribeDeadlineCheck).toBeDefined();
+        expect(res.subscribeDeadlineCheck.subscriptionId).toBeDefined();
+
+        const subscriptionId = res.subscribeDeadlineCheck.subscriptionId!.toString();
+        subscriptions.push(subscriptionId);
+
+        await expect.poll(async () => {
+            const mailhogResponse = await request.get(`${EMAIL_SERVICE}/api/v2/search?kind=containing&query=` + subscriptionEmail);
+            const emails = await mailhogResponse.json();
+            return emails.total;
+        }, {
+            message: 'Email should be found',
+            timeout: 60000,
+        }).toBeGreaterThan(0);
+
+        const mailhogResponse = await request.get(`${EMAIL_SERVICE}/api/v2/search?kind=containing&query=` + subscriptionEmail);
+        const emails = await mailhogResponse.json();
+        expect(emails.items[0].Content.Headers.To[0]).toBe(subscriptionEmail);
+        expect(emails.items[0].Content.Headers.Subject[0]).toContain(`PHDO DEADLINE MISSED NOTIFICATION`);
+    });
+
+    test('subscribing via webhook with duration cron', async ({ gql, request }) => {
+        const tokenRequest = await request.post(`${WEBHOOK_SERVICE_UI}/token`);
+        const token = await tokenRequest.json();
+        const webhookUrl = `${WEBHOOK_SERVICE}/${token.uuid}`;
+
+        const subscription = createDeadlineSubscriptionInput({
+            webhookUrl: webhookUrl,
+            cronSchedule: "@every 5s",
+            notificationType: NotificationType.Webhook
+        });
+
+        const res = await gql.subscribeDeadlineCheck({ subscription });
+        expect(res.subscribeDeadlineCheck).toBeDefined();
+        expect(res.subscribeDeadlineCheck.subscriptionId).toBeDefined();
+
+        const subscriptionId = res.subscribeDeadlineCheck.subscriptionId!.toString();
+        subscriptions.push(subscriptionId);
+        
+        await expect.poll(async () => {
+            const webhooksiteResponse = await request.get(`${WEBHOOK_SERVICE_UI}/token/${token.uuid}/requests`);
+            const webhookRequests = await webhooksiteResponse.json();
+            return webhookRequests.total
+        }, {
+            message: "Webhook should be called",
+            intervals: [5000],
+            timeout: 20000,
+        }).toBeGreaterThan(0);
+    });
+
+    test('subscribing via webhook with classic cron', async ({ gql, request }) => {
+        const tokenRequest = await request.post(`${WEBHOOK_SERVICE_UI}/token`);
+        const token = await tokenRequest.json();
+        const webhookUrl = `${WEBHOOK_SERVICE}/${token.uuid}`;
+
+        const subscription = createDeadlineSubscriptionInput({
+            webhookUrl: webhookUrl,
+            cronSchedule: "* * * * *",
+            notificationType: NotificationType.Webhook
+        });
+
+        const res = await gql.subscribeDeadlineCheck({ subscription });
+        expect(res.subscribeDeadlineCheck).toBeDefined();
+        expect(res.subscribeDeadlineCheck.subscriptionId).toBeDefined();
+
+        const subscriptionId = res.subscribeDeadlineCheck.subscriptionId!.toString();
+        subscriptions.push(subscriptionId);
+        
+        await expect.poll(async () => {
+            const webhooksiteResponse = await request.get(`${WEBHOOK_SERVICE_UI}/token/${token.uuid}/requests`);
+            const webhookRequests = await webhooksiteResponse.json();
+            return webhookRequests.total
+        }, {
+            message: "Webhook should be called",
+            intervals: [5000],
+            timeout: 600000,
+        }).toBeGreaterThan(0);
+    });
+
+    test('subscribing to a specific data stream via email', async ({ gql, request }) => {
+        const subscriptionEmail = `subscribeDeadlineCheck-datastream@test.com`;
+        const subscription = createDeadlineSubscriptionInput({
+            emailAddresses: [subscriptionEmail],
+            cronSchedule: "@every 10s",
+            dataStreamId: "dextesting",
+            dataStreamRoute: "testevent1",
+            expectedJurisdictions: ["jurisdiction"]
+        });
+
+        const res = await gql.subscribeDeadlineCheck({ subscription });
+        expect(res.subscribeDeadlineCheck).toBeDefined();
+        expect(res.subscribeDeadlineCheck.subscriptionId).toBeDefined();
+
+        const subscriptionId = res.subscribeDeadlineCheck.subscriptionId!.toString();
+        subscriptions.push(subscriptionId);
+        
+        await expect.poll(async () => {
+            const mailhogResponse = await request.get(`${EMAIL_SERVICE}/api/v2/search?kind=containing&query=` + subscriptionEmail);
+            const emails = await mailhogResponse.json();
+            return emails.total;
+        }, {
+            message: 'Email should be found',
+            timeout: 60000,
+        }).toBeGreaterThan(0);
+
+        const mailhogResponse = await request.get(`${EMAIL_SERVICE}/api/v2/search?kind=containing&query=` + subscriptionEmail);
+        const emails = await mailhogResponse.json();
+        expect(emails.items[0].Content.Headers.To[0]).toBe(subscriptionEmail);
+        expect(emails.items[0].Content.Headers.Subject[0]).toContain(`PHDO DEADLINE MISSED NOTIFICATION`);
+    });
+
+    test('subscribing with multiple emails', async ({ gql, request }) => {
+        const subscriptionEmail1 = `subscribeDeadlineCheck-multiple-emails-1@test.com`;
+        const subscriptionEmail2 = `subscribeDeadlineCheck-multiple-emails-2@test.com`;
+        const subscription = createDeadlineSubscriptionInput({
+            emailAddresses: [subscriptionEmail1, subscriptionEmail2],
+            cronSchedule: "@every 10s"
+        });
+
+        const res = await gql.subscribeDeadlineCheck({ subscription });
+        expect(res.subscribeDeadlineCheck).toBeDefined();
+        expect(res.subscribeDeadlineCheck.subscriptionId).toBeDefined();
+        
+        const subscriptionId = res.subscribeDeadlineCheck.subscriptionId!.toString();
+        subscriptions.push(subscriptionId);
+
+        await expect.poll(async () => {
+            const mailhogResponse = await request.get(`${EMAIL_SERVICE}/api/v2/search?kind=containing&query=` + subscriptionEmail1);
+            const emails = await mailhogResponse.json();
+            return emails.total;
+        }, {
+            message: 'First subscribed email should be found',
+            timeout: 60000,
+        }).toBeGreaterThan(0);
+
+        await expect.poll(async () => {
+            const mailhogResponse = await request.get(`${EMAIL_SERVICE}/api/v2/search?kind=containing&query=` + subscriptionEmail2);
+            const emails = await mailhogResponse.json();
+            return emails.total;
+        }, {
+            message: 'Second subscribed email should be found',
+            timeout: 60000,
+        }).toBeGreaterThan(0);
+        
+        const mailhogResponse1 = await request.get(`${EMAIL_SERVICE}/api/v2/search?kind=containing&query=` + subscriptionEmail1);
+        const emails1 = await mailhogResponse1.json();
+        expect(emails1.items[0].Content.Headers.Subject[0]).toContain(`PHDO DEADLINE MISSED NOTIFICATION`);
+
+        const mailhogResponse2 = await request.get(`${EMAIL_SERVICE}/api/v2/search?kind=containing&query=` + subscriptionEmail2);
+        const emails2 = await mailhogResponse2.json();
+        expect(emails2.items[0].Content.Headers.Subject[0]).toContain(`PHDO DEADLINE MISSED NOTIFICATION`);
+    });
+
+    test.describe('subscribing errors', () => {
+        test('invalid chron schedule', async ({ gql }) => {
+            const subscription = createDeadlineSubscriptionInput({
+                emailAddresses: [`subscribeDeadlineCheck-error-invalid-cron@test.com`],
+                cronSchedule: "INVALID"
+            });
+
+            const res = await gql.subscribeDeadlineCheck({ subscription }, { failOnEmptyData: false }) as unknown as GraphQLErrorResponse;
+            expect(JSON.stringify(res.errors)).toMatchSnapshot("invalid-cron");
+        });
+
+        test('invalid notification type', async ({ gql }) => {
+            const subscription = createDeadlineSubscriptionInput({
+                emailAddresses: [`subscribeDeadlineCheck-error-invalid-notification-type@test.com`],
+                notificationType: "INVALID" as unknown as NotificationType
+            });
+
+            const res = await gql.subscribeDeadlineCheck({ subscription }, { failOnEmptyData: false }) as unknown as GraphQLErrorResponse;
+            expect(JSON.stringify(res.errors)).toMatchSnapshot("invalid-notification-type");
+        });
+
+        test.skip('invalid email format', async ({ gql }) => {
+            const subscription = createDeadlineSubscriptionInput({
+                emailAddresses: [`subscribeDeadlineCheck-error-invalid-email@test.com`],
+                notificationType: NotificationType.Email
+            });
+
+            const res = await gql.subscribeDeadlineCheck({ subscription }, { failOnEmptyData: false }) as unknown as GraphQLErrorResponse;
+            expect(JSON.stringify(res.errors)).toMatchSnapshot("invalid-email-format");
+        });
+
+        test.skip('invalid webhook format', async ({ gql }) => {
+            const subscription = createDeadlineSubscriptionInput({
+                webhookUrl: "bad/webhook/url",
+                notificationType: NotificationType.Webhook
+            });
+
+            const res = await gql.subscribeDeadlineCheck({ subscription }, { failOnEmptyData: false }) as unknown as GraphQLErrorResponse;
+            expect(JSON.stringify(res.errors)).toMatchSnapshot("invalid-email-format");
+        });
+
+        test('invalid deadline value', async ({ gql }) => {
+            const subscription = createDeadlineSubscriptionInput({
+                emailAddresses: [`subscribeDeadlineCheck-error-invalid-deadline-type@test.com`],
+                deadlineTime: "INVALID"
+            });
+
+            const res = await gql.subscribeDeadlineCheck({ subscription }, { failOnEmptyData: false }) as unknown as GraphQLErrorResponse;
+            expect(JSON.stringify(res.errors)).toMatchSnapshot("invalid-notification-type");
+        });
+
+        test.skip('invalid webhook subscription with no url', async ({ gql }) => {
+            const subscription = createDeadlineSubscriptionInput({
+                notificationType: NotificationType.Webhook,
+                webhookUrl: null,
+                emailAddresses: ["subscribeDeadlineCheck-error-invalid-webhook-subscription-with-no-url@test.com"]
+            });
+            
+            const res = await gql.subscribeDeadlineCheck({ subscription }, { failOnEmptyData: false }) as unknown as GraphQLErrorResponse;
+            expect(JSON.stringify(res.errors)).toMatchSnapshot("invalid-webhook-subscription-with-no-url");
+        });
+
+        test.skip('invalid email subscription with no emails', async ({ gql }) => {
+            const subscription = createDeadlineSubscriptionInput({
+                notificationType: NotificationType.Email,
+                emailAddresses: [],
+                webhookUrl: WEBHOOK_SERVICE
+            });
+            
+            const res = await gql.subscribeDeadlineCheck({ subscription }, { failOnEmptyData: false }) as unknown as GraphQLErrorResponse;
+            expect(JSON.stringify(res.errors)).toMatchSnapshot("invalid-email-subscription-with-no-emails");
+        }); 
+    });
+});


Reviews:
user: login: cfarmer-fearless
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-19T18:29:49Z
user: login: cfarmer-fearless
body: Requesting change for Typescript maintainability improvement plus suggestion for additional error test.
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-19T18:34:53Z
user: login: cyber-decker
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-20T15:35:48Z
user: login: cyber-decker
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-20T15:54:41Z
user: login: cyber-decker
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-22T14:38:30Z
user: login: cyber-decker
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-22T14:40:28Z
user: login: cfarmer-fearless
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-22T15:02:06Z

Review comments:
diff_hunk: @@ -0,0 +1,271 @@
+import { test, expect } from '@fixtures/gql';
+import { NotificationType } from '@gql';
+import { GraphQLError } from 'graphql';
+import { createDeadlineSubscriptionInput } from '../fixtures/dataGenerator';
+
+const EMAIL_SERVICE = process.env.EMAILURL || "http://localhost:8025";
+const WEBHOOK_SERVICE = process.env.WEBHOOKURL || "http://webhook:80";
+const WEBHOOK_SERVICE_UI = process.env.WEBHOOKAPI || "http://localhost:8084";
+
+type GraphQLErrorResponse = { errors: GraphQLError[] };
+
+let subscriptions:string[] = []
+
+test.describe('GraphQL subscribeDeadlineCheck', () => {
+    test.setTimeout(90000); 
+
+    test.beforeAll(async ({ request }) => { 
+        const mailhogResponse = await request.delete(`${EMAIL_SERVICE}/api/v1/messages`);
+        expect(mailhogResponse.status()).toBe(200);
+    });
+
+    test.afterEach(async ({ gql }) => { 
+        subscriptions.forEach(async (subscriptionId) => {
+            const response = await gql.unsubscribeNotificationWorkflow({ subscriptionId: subscriptionId });
+            expect(response.unsubscribeNotificationWorkflow.subscriptionId).toBe(subscriptionId);
+        });
+        subscriptions = [];
+    });
+
+    test('subscribing via email with duration cron', async ({ gql, request }) => {
+        const subscriptionEmail = `subscribeDeadlineCheck-cron-duration@test.com`;
+        const subscription = createDeadlineSubscriptionInput({
+            emailAddresses: [subscriptionEmail],
+            cronSchedule: "@every 10s"
+        });
+
+        const res = await gql.subscribeDeadlineCheck({ subscription });
+        expect(res.subscribeDeadlineCheck).toBeDefined();
+        expect(res.subscribeDeadlineCheck.subscriptionId).toBeDefined();
+        
+        const subscriptionId = res.subscribeDeadlineCheck.subscriptionId!.toString();
+        subscriptions.push(subscriptionId);
+
+        await expect.poll(async () => {
+            const mailhogResponse = await request.get(`${EMAIL_SERVICE}/api/v2/search?kind=containing&query=` + subscriptionEmail);
+            const emails = await mailhogResponse.json();
+            return emails.total;
+        }, {
+            message: 'Email should be found',
+            timeout: 60000,
+        }).toBeGreaterThan(0);
+
+        const mailhogResponse = await request.get(`${EMAIL_SERVICE}/api/v2/search?kind=containing&query=` + subscriptionEmail);
+        const emails = await mailhogResponse.json();
+        expect(emails.items[0].Content.Headers.To[0]).toBe(subscriptionEmail);
+        expect(emails.items[0].Content.Headers.Subject[0]).toContain(`PHDO DEADLINE MISSED NOTIFICATION`);
+    });
+
+    test('subscribing via email with classic cron', async ({ gql, request }) => {        
+        const subscriptionEmail = `subscribeDeadlineCheck-cron-classic@test.com`;
+        const subscription = createDeadlineSubscriptionInput({
+            emailAddresses: [subscriptionEmail],
+            cronSchedule: "* * * * *"
+        });
+
+        const res = await gql.subscribeDeadlineCheck({ subscription });
+        expect(res.subscribeDeadlineCheck).toBeDefined();
+        expect(res.subscribeDeadlineCheck.subscriptionId).toBeDefined();
+
+        const subscriptionId = res.subscribeDeadlineCheck.subscriptionId!.toString();
+        subscriptions.push(subscriptionId);
+
+        await expect.poll(async () => {
+            const mailhogResponse = await request.get(`${EMAIL_SERVICE}/api/v2/search?kind=containing&query=` + subscriptionEmail);
+            const emails = await mailhogResponse.json();
+            return emails.total;
+        }, {
+            message: 'Email should be found',
+            timeout: 60000,
+        }).toBeGreaterThan(0);
+
+        const mailhogResponse = await request.get(`${EMAIL_SERVICE}/api/v2/search?kind=containing&query=` + subscriptionEmail);
+        const emails = await mailhogResponse.json();
+        expect(emails.items[0].Content.Headers.To[0]).toBe(subscriptionEmail);
+        expect(emails.items[0].Content.Headers.Subject[0]).toContain(`PHDO DEADLINE MISSED NOTIFICATION`);
+    });
+
+    test('subscribing via webhook with duration cron', async ({ gql, request }) => {
+        const tokenRequest = await request.post(`${WEBHOOK_SERVICE_UI}/token`);
+        const token = await tokenRequest.json();
+        const webhookUrl = `${WEBHOOK_SERVICE}/${token.uuid}`;
+
+        const subscription = createDeadlineSubscriptionInput({
+            webhookUrl: webhookUrl,
+            cronSchedule: "@every 5s",
+            notificationType: NotificationType.Webhook
+        });
+
+        const res = await gql.subscribeDeadlineCheck({ subscription });
+        expect(res.subscribeDeadlineCheck).toBeDefined();
+        expect(res.subscribeDeadlineCheck.subscriptionId).toBeDefined();
+
+        const subscriptionId = res.subscribeDeadlineCheck.subscriptionId!.toString();
+        subscriptions.push(subscriptionId);
+        
+        await expect.poll(async () => {
+            const webhooksiteResponse = await request.get(`${WEBHOOK_SERVICE_UI}/token/${token.uuid}/requests`);
+            const webhookRequests = await webhooksiteResponse.json();
+            return webhookRequests.total
+        }, {
+            message: "Webhook should be called",
+            intervals: [5000],
+            timeout: 20000,
+        }).toBeGreaterThan(0);
+    });
+
+    test('subscribing via webhook with classic cron', async ({ gql, request }) => {
+        const tokenRequest = await request.post(`${WEBHOOK_SERVICE_UI}/token`);
+        const token = await tokenRequest.json();
+        const webhookUrl = `${WEBHOOK_SERVICE}/${token.uuid}`;
+
+        const subscription = createDeadlineSubscriptionInput({
+            webhookUrl: webhookUrl,
+            cronSchedule: "* * * * *",
+            notificationType: NotificationType.Webhook
+        });
+
+        const res = await gql.subscribeDeadlineCheck({ subscription });
+        expect(res.subscribeDeadlineCheck).toBeDefined();
+        expect(res.subscribeDeadlineCheck.subscriptionId).toBeDefined();
+
+        const subscriptionId = res.subscribeDeadlineCheck.subscriptionId!.toString();
+        subscriptions.push(subscriptionId);
+        
+        await expect.poll(async () => {
+            const webhooksiteResponse = await request.get(`${WEBHOOK_SERVICE_UI}/token/${token.uuid}/requests`);
+            const webhookRequests = await webhooksiteResponse.json();
+            return webhookRequests.total
+        }, {
+            message: "Webhook should be called",
+            intervals: [5000],
+            timeout: 600000,
+        }).toBeGreaterThan(0);
+    });
+
+    test('subscribing to a specific data stream via email', async ({ gql, request }) => {
+        const subscriptionEmail = `subscribeDeadlineCheck-datastream@test.com`;
+        const subscription = createDeadlineSubscriptionInput({
+            emailAddresses: [subscriptionEmail],
+            cronSchedule: "@every 10s",
+            dataStreamId: "dextesting",
+            dataStreamRoute: "testevent1",
+            expectedJurisdictions: ["jurisdiction"]
+        });
+
+        const res = await gql.subscribeDeadlineCheck({ subscription });
+        expect(res.subscribeDeadlineCheck).toBeDefined();
+        expect(res.subscribeDeadlineCheck.subscriptionId).toBeDefined();
+
+        const subscriptionId = res.subscribeDeadlineCheck.subscriptionId!.toString();
+        subscriptions.push(subscriptionId);
+        
+        await expect.poll(async () => {
+            const mailhogResponse = await request.get(`${EMAIL_SERVICE}/api/v2/search?kind=containing&query=` + subscriptionEmail);
+            const emails = await mailhogResponse.json();
+            return emails.total;
+        }, {
+            message: 'Email should be found',
+            timeout: 60000,
+        }).toBeGreaterThan(0);
+
+        const mailhogResponse = await request.get(`${EMAIL_SERVICE}/api/v2/search?kind=containing&query=` + subscriptionEmail);
+        const emails = await mailhogResponse.json();
+        expect(emails.items[0].Content.Headers.To[0]).toBe(subscriptionEmail);
+        expect(emails.items[0].Content.Headers.Subject[0]).toContain(`PHDO DEADLINE MISSED NOTIFICATION`);
+    });
+
+    test('subscribing with multiple emails', async ({ gql, request }) => {
+        const subscriptionEmail1 = `subscribeDeadlineCheck-multiple-emails-1@test.com`;
+        const subscriptionEmail2 = `subscribeDeadlineCheck-multiple-emails-2@test.com`;
+        const subscription = createDeadlineSubscriptionInput({
+            emailAddresses: [subscriptionEmail1, subscriptionEmail2],
+            cronSchedule: "@every 10s"
+        });
+
+        const res = await gql.subscribeDeadlineCheck({ subscription });
+        expect(res.subscribeDeadlineCheck).toBeDefined();
+        expect(res.subscribeDeadlineCheck.subscriptionId).toBeDefined();
+        
+        const subscriptionId = res.subscribeDeadlineCheck.subscriptionId!.toString();
+        subscriptions.push(subscriptionId);
+
+        await expect.poll(async () => {
+            const mailhogResponse = await request.get(`${EMAIL_SERVICE}/api/v2/search?kind=containing&query=` + subscriptionEmail1);
+            const emails = await mailhogResponse.json();
+            return emails.total;
+        }, {
+            message: 'First subscribed email should be found',
+            timeout: 60000,
+        }).toBeGreaterThan(0);
+
+        await expect.poll(async () => {
+            const mailhogResponse = await request.get(`${EMAIL_SERVICE}/api/v2/search?kind=containing&query=` + subscriptionEmail2);
+            const emails = await mailhogResponse.json();
+            return emails.total;
+        }, {
+            message: 'Second subscribed email should be found',
+            timeout: 60000,
+        }).toBeGreaterThan(0);
+        
+        const mailhogResponse1 = await request.get(`${EMAIL_SERVICE}/api/v2/search?kind=containing&query=` + subscriptionEmail1);
+        const emails1 = await mailhogResponse1.json();
+        expect(emails1.items[0].Content.Headers.Subject[0]).toContain(`PHDO DEADLINE MISSED NOTIFICATION`);
+
+        const mailhogResponse2 = await request.get(`${EMAIL_SERVICE}/api/v2/search?kind=containing&query=` + subscriptionEmail2);
+        const emails2 = await mailhogResponse2.json();
+        expect(emails2.items[0].Content.Headers.Subject[0]).toContain(`PHDO DEADLINE MISSED NOTIFICATION`);
+    });
+
+    test.describe('subscribing errors', () => {
path: test/playwright/tests/subscribeDeadlineCheck.spec.ts
user: login: cfarmer-fearless
body: May want to add a test for when there is a mismatch between the notification type and notification destination.  For example, notification type set to webhook and I specify a list of emails, and vice versa.
created_at: 2025-05-19T18:29:49Z
author_association: CONTRIBUTOR
diff_hunk: @@ -264,6 +264,39 @@ export function createSubscriptionInput({
     };
 }
 
+export function createDeadlineSubscriptionInput({
+    emailAddresses = [],
+    cronSchedule = "0 0 1 12 *",
+    dataStreamId = "",
+    dataStreamRoute = "",
+    deadlineTime = "06:00:00",
+    expectedJurisdictions = [],
+    notificationType = NotificationType.Email,
+    webhookUrl = "",
+}: {
path: test/playwright/fixtures/dataGenerator.ts
user: login: cfarmer-fearless
body: Use the `Partial<T>` utility type for the input type here so that you don't have to update this input type if `WorkflowSubscriptionDeadlineCheckInput` changes.
created_at: 2025-05-19T18:33:51Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,271 @@
+import { test, expect } from '@fixtures/gql';
+import { NotificationType } from '@gql';
+import { GraphQLError } from 'graphql';
+import { createDeadlineSubscriptionInput } from '../fixtures/dataGenerator';
+
+const EMAIL_SERVICE = process.env.EMAILURL || "http://localhost:8025";
+const WEBHOOK_SERVICE = process.env.WEBHOOKURL || "http://webhook:80";
+const WEBHOOK_SERVICE_UI = process.env.WEBHOOKAPI || "http://localhost:8084";
+
+type GraphQLErrorResponse = { errors: GraphQLError[] };
+
+let subscriptions:string[] = []
+
+test.describe('GraphQL subscribeDeadlineCheck', () => {
+    test.setTimeout(90000); 
+
+    test.beforeAll(async ({ request }) => { 
+        const mailhogResponse = await request.delete(`${EMAIL_SERVICE}/api/v1/messages`);
+        expect(mailhogResponse.status()).toBe(200);
+    });
+
+    test.afterEach(async ({ gql }) => { 
+        subscriptions.forEach(async (subscriptionId) => {
+            const response = await gql.unsubscribeNotificationWorkflow({ subscriptionId: subscriptionId });
+            expect(response.unsubscribeNotificationWorkflow.subscriptionId).toBe(subscriptionId);
+        });
+        subscriptions = [];
+    });
+
+    test('subscribing via email with duration cron', async ({ gql, request }) => {
+        const subscriptionEmail = `subscribeDeadlineCheck-cron-duration@test.com`;
+        const subscription = createDeadlineSubscriptionInput({
+            emailAddresses: [subscriptionEmail],
+            cronSchedule: "@every 10s"
+        });
+
+        const res = await gql.subscribeDeadlineCheck({ subscription });
+        expect(res.subscribeDeadlineCheck).toBeDefined();
+        expect(res.subscribeDeadlineCheck.subscriptionId).toBeDefined();
+        
+        const subscriptionId = res.subscribeDeadlineCheck.subscriptionId!.toString();
+        subscriptions.push(subscriptionId);
+
+        await expect.poll(async () => {
+            const mailhogResponse = await request.get(`${EMAIL_SERVICE}/api/v2/search?kind=containing&query=` + subscriptionEmail);
+            const emails = await mailhogResponse.json();
+            return emails.total;
+        }, {
+            message: 'Email should be found',
+            timeout: 60000,
+        }).toBeGreaterThan(0);
+
+        const mailhogResponse = await request.get(`${EMAIL_SERVICE}/api/v2/search?kind=containing&query=` + subscriptionEmail);
+        const emails = await mailhogResponse.json();
+        expect(emails.items[0].Content.Headers.To[0]).toBe(subscriptionEmail);
+        expect(emails.items[0].Content.Headers.Subject[0]).toContain(`PHDO DEADLINE MISSED NOTIFICATION`);
+    });
+
+    test('subscribing via email with classic cron', async ({ gql, request }) => {        
+        const subscriptionEmail = `subscribeDeadlineCheck-cron-classic@test.com`;
+        const subscription = createDeadlineSubscriptionInput({
+            emailAddresses: [subscriptionEmail],
+            cronSchedule: "* * * * *"
+        });
+
+        const res = await gql.subscribeDeadlineCheck({ subscription });
+        expect(res.subscribeDeadlineCheck).toBeDefined();
+        expect(res.subscribeDeadlineCheck.subscriptionId).toBeDefined();
+
+        const subscriptionId = res.subscribeDeadlineCheck.subscriptionId!.toString();
+        subscriptions.push(subscriptionId);
+
+        await expect.poll(async () => {
+            const mailhogResponse = await request.get(`${EMAIL_SERVICE}/api/v2/search?kind=containing&query=` + subscriptionEmail);
+            const emails = await mailhogResponse.json();
+            return emails.total;
+        }, {
+            message: 'Email should be found',
+            timeout: 60000,
+        }).toBeGreaterThan(0);
+
+        const mailhogResponse = await request.get(`${EMAIL_SERVICE}/api/v2/search?kind=containing&query=` + subscriptionEmail);
+        const emails = await mailhogResponse.json();
+        expect(emails.items[0].Content.Headers.To[0]).toBe(subscriptionEmail);
+        expect(emails.items[0].Content.Headers.Subject[0]).toContain(`PHDO DEADLINE MISSED NOTIFICATION`);
+    });
+
+    test('subscribing via webhook with duration cron', async ({ gql, request }) => {
+        const tokenRequest = await request.post(`${WEBHOOK_SERVICE_UI}/token`);
+        const token = await tokenRequest.json();
+        const webhookUrl = `${WEBHOOK_SERVICE}/${token.uuid}`;
+
+        const subscription = createDeadlineSubscriptionInput({
+            webhookUrl: webhookUrl,
+            cronSchedule: "@every 5s",
+            notificationType: NotificationType.Webhook
+        });
+
+        const res = await gql.subscribeDeadlineCheck({ subscription });
+        expect(res.subscribeDeadlineCheck).toBeDefined();
+        expect(res.subscribeDeadlineCheck.subscriptionId).toBeDefined();
+
+        const subscriptionId = res.subscribeDeadlineCheck.subscriptionId!.toString();
+        subscriptions.push(subscriptionId);
+        
+        await expect.poll(async () => {
+            const webhooksiteResponse = await request.get(`${WEBHOOK_SERVICE_UI}/token/${token.uuid}/requests`);
+            const webhookRequests = await webhooksiteResponse.json();
+            return webhookRequests.total
+        }, {
+            message: "Webhook should be called",
+            intervals: [5000],
+            timeout: 20000,
+        }).toBeGreaterThan(0);
+    });
+
+    test('subscribing via webhook with classic cron', async ({ gql, request }) => {
+        const tokenRequest = await request.post(`${WEBHOOK_SERVICE_UI}/token`);
+        const token = await tokenRequest.json();
+        const webhookUrl = `${WEBHOOK_SERVICE}/${token.uuid}`;
+
+        const subscription = createDeadlineSubscriptionInput({
+            webhookUrl: webhookUrl,
+            cronSchedule: "* * * * *",
+            notificationType: NotificationType.Webhook
+        });
+
+        const res = await gql.subscribeDeadlineCheck({ subscription });
+        expect(res.subscribeDeadlineCheck).toBeDefined();
+        expect(res.subscribeDeadlineCheck.subscriptionId).toBeDefined();
+
+        const subscriptionId = res.subscribeDeadlineCheck.subscriptionId!.toString();
+        subscriptions.push(subscriptionId);
+        
+        await expect.poll(async () => {
+            const webhooksiteResponse = await request.get(`${WEBHOOK_SERVICE_UI}/token/${token.uuid}/requests`);
+            const webhookRequests = await webhooksiteResponse.json();
+            return webhookRequests.total
+        }, {
+            message: "Webhook should be called",
+            intervals: [5000],
+            timeout: 600000,
+        }).toBeGreaterThan(0);
+    });
+
+    test('subscribing to a specific data stream via email', async ({ gql, request }) => {
+        const subscriptionEmail = `subscribeDeadlineCheck-datastream@test.com`;
+        const subscription = createDeadlineSubscriptionInput({
+            emailAddresses: [subscriptionEmail],
+            cronSchedule: "@every 10s",
+            dataStreamId: "dextesting",
+            dataStreamRoute: "testevent1",
+            expectedJurisdictions: ["jurisdiction"]
+        });
+
+        const res = await gql.subscribeDeadlineCheck({ subscription });
+        expect(res.subscribeDeadlineCheck).toBeDefined();
+        expect(res.subscribeDeadlineCheck.subscriptionId).toBeDefined();
+
+        const subscriptionId = res.subscribeDeadlineCheck.subscriptionId!.toString();
+        subscriptions.push(subscriptionId);
+        
+        await expect.poll(async () => {
+            const mailhogResponse = await request.get(`${EMAIL_SERVICE}/api/v2/search?kind=containing&query=` + subscriptionEmail);
+            const emails = await mailhogResponse.json();
+            return emails.total;
+        }, {
+            message: 'Email should be found',
+            timeout: 60000,
+        }).toBeGreaterThan(0);
+
+        const mailhogResponse = await request.get(`${EMAIL_SERVICE}/api/v2/search?kind=containing&query=` + subscriptionEmail);
+        const emails = await mailhogResponse.json();
+        expect(emails.items[0].Content.Headers.To[0]).toBe(subscriptionEmail);
+        expect(emails.items[0].Content.Headers.Subject[0]).toContain(`PHDO DEADLINE MISSED NOTIFICATION`);
+    });
+
+    test('subscribing with multiple emails', async ({ gql, request }) => {
+        const subscriptionEmail1 = `subscribeDeadlineCheck-multiple-emails-1@test.com`;
+        const subscriptionEmail2 = `subscribeDeadlineCheck-multiple-emails-2@test.com`;
+        const subscription = createDeadlineSubscriptionInput({
+            emailAddresses: [subscriptionEmail1, subscriptionEmail2],
+            cronSchedule: "@every 10s"
+        });
+
+        const res = await gql.subscribeDeadlineCheck({ subscription });
+        expect(res.subscribeDeadlineCheck).toBeDefined();
+        expect(res.subscribeDeadlineCheck.subscriptionId).toBeDefined();
+        
+        const subscriptionId = res.subscribeDeadlineCheck.subscriptionId!.toString();
+        subscriptions.push(subscriptionId);
+
+        await expect.poll(async () => {
+            const mailhogResponse = await request.get(`${EMAIL_SERVICE}/api/v2/search?kind=containing&query=` + subscriptionEmail1);
+            const emails = await mailhogResponse.json();
+            return emails.total;
+        }, {
+            message: 'First subscribed email should be found',
+            timeout: 60000,
+        }).toBeGreaterThan(0);
+
+        await expect.poll(async () => {
+            const mailhogResponse = await request.get(`${EMAIL_SERVICE}/api/v2/search?kind=containing&query=` + subscriptionEmail2);
+            const emails = await mailhogResponse.json();
+            return emails.total;
+        }, {
+            message: 'Second subscribed email should be found',
+            timeout: 60000,
+        }).toBeGreaterThan(0);
+        
+        const mailhogResponse1 = await request.get(`${EMAIL_SERVICE}/api/v2/search?kind=containing&query=` + subscriptionEmail1);
+        const emails1 = await mailhogResponse1.json();
+        expect(emails1.items[0].Content.Headers.Subject[0]).toContain(`PHDO DEADLINE MISSED NOTIFICATION`);
+
+        const mailhogResponse2 = await request.get(`${EMAIL_SERVICE}/api/v2/search?kind=containing&query=` + subscriptionEmail2);
+        const emails2 = await mailhogResponse2.json();
+        expect(emails2.items[0].Content.Headers.Subject[0]).toContain(`PHDO DEADLINE MISSED NOTIFICATION`);
+    });
+
+    test.describe('subscribing errors', () => {
path: test/playwright/tests/subscribeDeadlineCheck.spec.ts
user: login: cyber-decker
body: @mkrystof - might need a little bit of guidance here for this suggestion. Right now it seems like we can use the subscribeDeadlineCheck mutation to do both Email and Webhook subscriptions and Chase gives a really interesting situation above.

Right now, both the `emailAddresses` property and the `webhookUrl` property can both be null when the notification type is either WEBHOOK or EMAIL.  Both of these subscriptions are allowed and go through and when no email or webhookurl are provided (or at least if they are blank/null) the subscription just does nothing.  

Based on that, what would be the expectation if we subscribe, with a notification type of "WEBHOOK" but the webhook field is "null".   Should this be an error?  We can't really test to see that no webhook comes through.  If we provide an email instead of a webhook URL i suppose we could check that no email comes through, but this isn't exactly a great test and is also costly in terms of time to give it time to possibly come through but never coming through.

Should we have better validation and/or an error behavior here?
created_at: 2025-05-20T15:35:48Z
author_association: COLLABORATOR
diff_hunk: @@ -264,6 +264,39 @@ export function createSubscriptionInput({
     };
 }
 
+export function createDeadlineSubscriptionInput({
+    emailAddresses = [],
+    cronSchedule = "0 0 1 12 *",
+    dataStreamId = "",
+    dataStreamRoute = "",
+    deadlineTime = "06:00:00",
+    expectedJurisdictions = [],
+    notificationType = NotificationType.Email,
+    webhookUrl = "",
+}: {
path: test/playwright/fixtures/dataGenerator.ts
user: login: cyber-decker
body: I'm not sure if this is the right thing to do here.  If WorkflowSubscriptionDeadlineInput does change, I think we need to know that and not let it just get glossed over, right?  Codegen will create WorkflowSubscriptionDeadlineInput again for us and will set the required/optional fields automatically based on the schema.   Most of the fields for this are required.  I don't want the fields to be optional or have a smaller subset of fields that could be set for WorkflowSubscriptionDeadlineCheckInput.   

Additonally, using `Partial<WorkflowSubscriptionDeadlineCheckInput>` here seems to send the wrong message to when the created subscription is being used and the usages of it seem to scream because the `gql.subscribeDeadlineCheck()` functions expect the exact WorkflowSubscriptionDeadlineCheckInput type.   I don't think we want partial here unless I'm missing some other benefit of this? 
created_at: 2025-05-20T15:54:41Z
author_association: COLLABORATOR
diff_hunk: @@ -264,6 +264,39 @@ export function createSubscriptionInput({
     };
 }
 
+export function createDeadlineSubscriptionInput({
+    emailAddresses = [],
+    cronSchedule = "0 0 1 12 *",
+    dataStreamId = "",
+    dataStreamRoute = "",
+    deadlineTime = "06:00:00",
+    expectedJurisdictions = [],
+    notificationType = NotificationType.Email,
+    webhookUrl = "",
+}: {
path: test/playwright/fixtures/dataGenerator.ts
user: login: cyber-decker
body: Worked w/ Chase and we sorted this out.  I misunderstood and put this on the output rather than the input type!  Makes a lot more sense now.  Thanks for the good suggestion.
created_at: 2025-05-22T14:38:30Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,271 @@
+import { test, expect } from '@fixtures/gql';
+import { NotificationType } from '@gql';
+import { GraphQLError } from 'graphql';
+import { createDeadlineSubscriptionInput } from '../fixtures/dataGenerator';
+
+const EMAIL_SERVICE = process.env.EMAILURL || "http://localhost:8025";
+const WEBHOOK_SERVICE = process.env.WEBHOOKURL || "http://webhook:80";
+const WEBHOOK_SERVICE_UI = process.env.WEBHOOKAPI || "http://localhost:8084";
+
+type GraphQLErrorResponse = { errors: GraphQLError[] };
+
+let subscriptions:string[] = []
+
+test.describe('GraphQL subscribeDeadlineCheck', () => {
+    test.setTimeout(90000); 
+
+    test.beforeAll(async ({ request }) => { 
+        const mailhogResponse = await request.delete(`${EMAIL_SERVICE}/api/v1/messages`);
+        expect(mailhogResponse.status()).toBe(200);
+    });
+
+    test.afterEach(async ({ gql }) => { 
+        subscriptions.forEach(async (subscriptionId) => {
+            const response = await gql.unsubscribeNotificationWorkflow({ subscriptionId: subscriptionId });
+            expect(response.unsubscribeNotificationWorkflow.subscriptionId).toBe(subscriptionId);
+        });
+        subscriptions = [];
+    });
+
+    test('subscribing via email with duration cron', async ({ gql, request }) => {
+        const subscriptionEmail = `subscribeDeadlineCheck-cron-duration@test.com`;
+        const subscription = createDeadlineSubscriptionInput({
+            emailAddresses: [subscriptionEmail],
+            cronSchedule: "@every 10s"
+        });
+
+        const res = await gql.subscribeDeadlineCheck({ subscription });
+        expect(res.subscribeDeadlineCheck).toBeDefined();
+        expect(res.subscribeDeadlineCheck.subscriptionId).toBeDefined();
+        
+        const subscriptionId = res.subscribeDeadlineCheck.subscriptionId!.toString();
+        subscriptions.push(subscriptionId);
+
+        await expect.poll(async () => {
+            const mailhogResponse = await request.get(`${EMAIL_SERVICE}/api/v2/search?kind=containing&query=` + subscriptionEmail);
+            const emails = await mailhogResponse.json();
+            return emails.total;
+        }, {
+            message: 'Email should be found',
+            timeout: 60000,
+        }).toBeGreaterThan(0);
+
+        const mailhogResponse = await request.get(`${EMAIL_SERVICE}/api/v2/search?kind=containing&query=` + subscriptionEmail);
+        const emails = await mailhogResponse.json();
+        expect(emails.items[0].Content.Headers.To[0]).toBe(subscriptionEmail);
+        expect(emails.items[0].Content.Headers.Subject[0]).toContain(`PHDO DEADLINE MISSED NOTIFICATION`);
+    });
+
+    test('subscribing via email with classic cron', async ({ gql, request }) => {        
+        const subscriptionEmail = `subscribeDeadlineCheck-cron-classic@test.com`;
+        const subscription = createDeadlineSubscriptionInput({
+            emailAddresses: [subscriptionEmail],
+            cronSchedule: "* * * * *"
+        });
+
+        const res = await gql.subscribeDeadlineCheck({ subscription });
+        expect(res.subscribeDeadlineCheck).toBeDefined();
+        expect(res.subscribeDeadlineCheck.subscriptionId).toBeDefined();
+
+        const subscriptionId = res.subscribeDeadlineCheck.subscriptionId!.toString();
+        subscriptions.push(subscriptionId);
+
+        await expect.poll(async () => {
+            const mailhogResponse = await request.get(`${EMAIL_SERVICE}/api/v2/search?kind=containing&query=` + subscriptionEmail);
+            const emails = await mailhogResponse.json();
+            return emails.total;
+        }, {
+            message: 'Email should be found',
+            timeout: 60000,
+        }).toBeGreaterThan(0);
+
+        const mailhogResponse = await request.get(`${EMAIL_SERVICE}/api/v2/search?kind=containing&query=` + subscriptionEmail);
+        const emails = await mailhogResponse.json();
+        expect(emails.items[0].Content.Headers.To[0]).toBe(subscriptionEmail);
+        expect(emails.items[0].Content.Headers.Subject[0]).toContain(`PHDO DEADLINE MISSED NOTIFICATION`);
+    });
+
+    test('subscribing via webhook with duration cron', async ({ gql, request }) => {
+        const tokenRequest = await request.post(`${WEBHOOK_SERVICE_UI}/token`);
+        const token = await tokenRequest.json();
+        const webhookUrl = `${WEBHOOK_SERVICE}/${token.uuid}`;
+
+        const subscription = createDeadlineSubscriptionInput({
+            webhookUrl: webhookUrl,
+            cronSchedule: "@every 5s",
+            notificationType: NotificationType.Webhook
+        });
+
+        const res = await gql.subscribeDeadlineCheck({ subscription });
+        expect(res.subscribeDeadlineCheck).toBeDefined();
+        expect(res.subscribeDeadlineCheck.subscriptionId).toBeDefined();
+
+        const subscriptionId = res.subscribeDeadlineCheck.subscriptionId!.toString();
+        subscriptions.push(subscriptionId);
+        
+        await expect.poll(async () => {
+            const webhooksiteResponse = await request.get(`${WEBHOOK_SERVICE_UI}/token/${token.uuid}/requests`);
+            const webhookRequests = await webhooksiteResponse.json();
+            return webhookRequests.total
+        }, {
+            message: "Webhook should be called",
+            intervals: [5000],
+            timeout: 20000,
+        }).toBeGreaterThan(0);
+    });
+
+    test('subscribing via webhook with classic cron', async ({ gql, request }) => {
+        const tokenRequest = await request.post(`${WEBHOOK_SERVICE_UI}/token`);
+        const token = await tokenRequest.json();
+        const webhookUrl = `${WEBHOOK_SERVICE}/${token.uuid}`;
+
+        const subscription = createDeadlineSubscriptionInput({
+            webhookUrl: webhookUrl,
+            cronSchedule: "* * * * *",
+            notificationType: NotificationType.Webhook
+        });
+
+        const res = await gql.subscribeDeadlineCheck({ subscription });
+        expect(res.subscribeDeadlineCheck).toBeDefined();
+        expect(res.subscribeDeadlineCheck.subscriptionId).toBeDefined();
+
+        const subscriptionId = res.subscribeDeadlineCheck.subscriptionId!.toString();
+        subscriptions.push(subscriptionId);
+        
+        await expect.poll(async () => {
+            const webhooksiteResponse = await request.get(`${WEBHOOK_SERVICE_UI}/token/${token.uuid}/requests`);
+            const webhookRequests = await webhooksiteResponse.json();
+            return webhookRequests.total
+        }, {
+            message: "Webhook should be called",
+            intervals: [5000],
+            timeout: 600000,
+        }).toBeGreaterThan(0);
+    });
+
+    test('subscribing to a specific data stream via email', async ({ gql, request }) => {
+        const subscriptionEmail = `subscribeDeadlineCheck-datastream@test.com`;
+        const subscription = createDeadlineSubscriptionInput({
+            emailAddresses: [subscriptionEmail],
+            cronSchedule: "@every 10s",
+            dataStreamId: "dextesting",
+            dataStreamRoute: "testevent1",
+            expectedJurisdictions: ["jurisdiction"]
+        });
+
+        const res = await gql.subscribeDeadlineCheck({ subscription });
+        expect(res.subscribeDeadlineCheck).toBeDefined();
+        expect(res.subscribeDeadlineCheck.subscriptionId).toBeDefined();
+
+        const subscriptionId = res.subscribeDeadlineCheck.subscriptionId!.toString();
+        subscriptions.push(subscriptionId);
+        
+        await expect.poll(async () => {
+            const mailhogResponse = await request.get(`${EMAIL_SERVICE}/api/v2/search?kind=containing&query=` + subscriptionEmail);
+            const emails = await mailhogResponse.json();
+            return emails.total;
+        }, {
+            message: 'Email should be found',
+            timeout: 60000,
+        }).toBeGreaterThan(0);
+
+        const mailhogResponse = await request.get(`${EMAIL_SERVICE}/api/v2/search?kind=containing&query=` + subscriptionEmail);
+        const emails = await mailhogResponse.json();
+        expect(emails.items[0].Content.Headers.To[0]).toBe(subscriptionEmail);
+        expect(emails.items[0].Content.Headers.Subject[0]).toContain(`PHDO DEADLINE MISSED NOTIFICATION`);
+    });
+
+    test('subscribing with multiple emails', async ({ gql, request }) => {
+        const subscriptionEmail1 = `subscribeDeadlineCheck-multiple-emails-1@test.com`;
+        const subscriptionEmail2 = `subscribeDeadlineCheck-multiple-emails-2@test.com`;
+        const subscription = createDeadlineSubscriptionInput({
+            emailAddresses: [subscriptionEmail1, subscriptionEmail2],
+            cronSchedule: "@every 10s"
+        });
+
+        const res = await gql.subscribeDeadlineCheck({ subscription });
+        expect(res.subscribeDeadlineCheck).toBeDefined();
+        expect(res.subscribeDeadlineCheck.subscriptionId).toBeDefined();
+        
+        const subscriptionId = res.subscribeDeadlineCheck.subscriptionId!.toString();
+        subscriptions.push(subscriptionId);
+
+        await expect.poll(async () => {
+            const mailhogResponse = await request.get(`${EMAIL_SERVICE}/api/v2/search?kind=containing&query=` + subscriptionEmail1);
+            const emails = await mailhogResponse.json();
+            return emails.total;
+        }, {
+            message: 'First subscribed email should be found',
+            timeout: 60000,
+        }).toBeGreaterThan(0);
+
+        await expect.poll(async () => {
+            const mailhogResponse = await request.get(`${EMAIL_SERVICE}/api/v2/search?kind=containing&query=` + subscriptionEmail2);
+            const emails = await mailhogResponse.json();
+            return emails.total;
+        }, {
+            message: 'Second subscribed email should be found',
+            timeout: 60000,
+        }).toBeGreaterThan(0);
+        
+        const mailhogResponse1 = await request.get(`${EMAIL_SERVICE}/api/v2/search?kind=containing&query=` + subscriptionEmail1);
+        const emails1 = await mailhogResponse1.json();
+        expect(emails1.items[0].Content.Headers.Subject[0]).toContain(`PHDO DEADLINE MISSED NOTIFICATION`);
+
+        const mailhogResponse2 = await request.get(`${EMAIL_SERVICE}/api/v2/search?kind=containing&query=` + subscriptionEmail2);
+        const emails2 = await mailhogResponse2.json();
+        expect(emails2.items[0].Content.Headers.Subject[0]).toContain(`PHDO DEADLINE MISSED NOTIFICATION`);
+    });
+
+    test.describe('subscribing errors', () => {
path: test/playwright/tests/subscribeDeadlineCheck.spec.ts
user: login: cyber-decker
body: Added some tests in for this, but they will be currently skipped.  The validation for this doesn't catch these issues and it SHOULD be erroring.  I put in two additional tests to handle a webhook subscription with a null webhook url (and defined email) and an email subscription with null emails (and a defined webhook url).  These currently both submit, but should error.

There are some other tests too already for invalid email formats and invalid webhook url's as well, but they are also skipped for the same reason.
created_at: 2025-05-22T14:40:28Z
author_association: COLLABORATOR

PR comments:
user: login: sonarqubecloud[bot]
created_at: 2025-05-22T14:38:27Z
body: ## [![Quality Gate Failed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-failed-20px.png 'Quality Gate Failed')](https://sonarcloud.io/dashboard?id=CDCgov_data-exchange-processing-status&pullRequest=373) **Quality Gate failed**  
Failed conditions  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/failed-16px.png '') [1 Security Hotspot](https://sonarcloud.io/project/security_hotspots?id=CDCgov_data-exchange-processing-status&pullRequest=373&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/failed-16px.png '') [50.3% Duplication on New Code](https://sonarcloud.io/component_measures?id=CDCgov_data-exchange-processing-status&pullRequest=373&metric=new_duplicated_lines_density&view=list) (required ≤ 3%)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=CDCgov_data-exchange-processing-status&pullRequest=373)


author_association: NONE