Files

401 lines
16 KiB
TypeScript

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {expect, test} from './ai_bridge_fixture';
import {createUnreadChannelFixture, setupRecapBridge} from './recaps_helpers';
const MONDAY = 1 << 1;
const WEDNESDAY = 1 << 3;
/**
* @objective Verify a user can create, list, pause, and resume a scheduled AI recap
*/
test('creates and manages a scheduled recap', {tag: '@ai_recaps'}, async ({pw}) => {
const recapTitle = `Scheduled recap ${pw.random.id()}`;
const sourceMessage = `Scheduled recap source ${pw.random.id()}`;
// # Initialize the test server state, configure a deterministic recap agent, and seed a channel.
const {adminClient, adminUser, team, user, userClient} = await pw.initSetup();
if (!adminUser) {
throw new Error('Failed to create admin user');
}
const {agent} = await setupRecapBridge(pw, adminClient, {
completions: [],
});
const channel = await createUnreadChannelFixture(
pw,
adminClient,
adminUser.id,
user.id,
team.id,
'Scheduled recap channel',
sourceMessage,
);
// # Create a selected-channel scheduled recap through the modal flow.
const {recapsPage} = await pw.testBrowser.login(user);
await recapsPage.goto(team.name);
await recapsPage.toBeVisible();
const createRecapModal = await recapsPage.openCreateRecap();
await createRecapModal.fillTitle(recapTitle);
await createRecapModal.selectSelectedChannels();
await createRecapModal.clickNext();
await createRecapModal.expectChannelSelectorVisible();
await createRecapModal.searchChannel(channel.display_name);
await createRecapModal.selectChannel(channel.display_name);
await createRecapModal.clickNext();
await createRecapModal.expectScheduleConfigurationVisible();
await createRecapModal.selectScheduleDay('M');
await createRecapModal.createSchedule();
// * Verify the scheduled recap is listed with stable schedule structure and active state.
await recapsPage.toBeVisible();
await recapsPage.switchToScheduled();
const scheduledRecap = recapsPage.getScheduledRecap(recapTitle);
await scheduledRecap.toBeVisible();
await scheduledRecap.expectSchedulePattern(/Mon|Monday/);
await scheduledRecap.expectSchedulePattern(/ at /);
await scheduledRecap.expectActive();
// * Verify pause and resume update both the UI and the stored scheduled recap.
await scheduledRecap.pause();
await scheduledRecap.resume();
const scheduledRecaps = await userClient.getScheduledRecaps(0, 60);
const createdRecap = scheduledRecaps.find((recap) => recap.title === recapTitle);
expect(createdRecap).toBeDefined();
expect(createdRecap?.agent_id).toBe(agent.id);
expect(createdRecap?.channel_ids).toContain(channel.id);
expect(createdRecap?.days_of_week).toBe(MONDAY);
expect(createdRecap?.enabled).toBe(true);
});
/**
* @objective Verify the scheduled recap limit rejects creating more active scheduled recaps than configured
*/
test('rejects scheduled recap creation after the active schedule limit', {tag: '@ai_recaps'}, async ({pw}) => {
const titlePrefix = `Limited scheduled recap ${pw.random.id()}`;
const sourceMessage = `Scheduled limit source ${pw.random.id()}`;
// # Initialize an isolated user, configure the recap bridge, and set a one-scheduled-recap limit.
const {adminClient, adminUser, team, user, userClient} = await pw.initSetup();
if (!adminUser) {
throw new Error('Failed to create admin user');
}
const {agent} = await setupRecapBridge(pw, adminClient, {
completions: [],
});
const originalConfig = await adminClient.getConfig();
try {
await adminClient.patchConfig({
AIRecapSettings: {
EnforceScheduledRecaps: true,
DefaultLimits: {
MaxScheduledRecaps: 1,
},
},
});
const channel = await createUnreadChannelFixture(
pw,
adminClient,
adminUser.id,
user.id,
team.id,
'Scheduled limit recap channel',
sourceMessage,
);
const scheduledRecapInput = {
days_of_week: WEDNESDAY,
time_of_day: '09:00',
timezone: 'UTC',
time_period: 'last_24h' as const,
channel_mode: 'specific' as const,
channel_ids: [channel.id],
agent_id: agent.id,
is_recurring: true,
};
const allowedRecap = await userClient.createScheduledRecap({
...scheduledRecapInput,
title: `${titlePrefix} allowed`,
});
let blockedError: {status_code?: number; message?: string} | undefined;
try {
await userClient.createScheduledRecap({
...scheduledRecapInput,
title: `${titlePrefix} blocked`,
});
} catch (error) {
blockedError = error as {status_code?: number; message?: string};
}
// * Verify the API rejects the over-limit schedule without creating the second recap.
expect(blockedError?.status_code).toBe(400);
expect(blockedError?.message).toContain('scheduled recaps');
const scheduledRecaps = await userClient.getScheduledRecaps(0, 60);
expect(scheduledRecaps.some((recap) => recap.id === allowedRecap.id)).toBe(true);
expect(scheduledRecaps.some((recap) => recap.title === `${titlePrefix} blocked`)).toBe(false);
} finally {
await adminClient.updateConfig(originalConfig as any);
}
});
/**
* @objective Verify a user can edit a scheduled recap's title and schedule days through the Options menu
*/
test('edits a scheduled recap through the options menu', {tag: '@ai_recaps'}, async ({pw}) => {
const originalTitle = `Editable scheduled recap ${pw.random.id()}`;
const updatedTitle = `Edited scheduled recap ${pw.random.id()}`;
const sourceMessage = `Edit scheduled source ${pw.random.id()}`;
// # Initialize the test server state, configure the recap bridge, and seed a channel.
const {adminClient, adminUser, team, user, userClient} = await pw.initSetup();
if (!adminUser) {
throw new Error('Failed to create admin user');
}
const {agent} = await setupRecapBridge(pw, adminClient, {
completions: [],
});
const channel = await createUnreadChannelFixture(
pw,
adminClient,
adminUser.id,
user.id,
team.id,
'Edit scheduled recap channel',
sourceMessage,
);
// # Create a Monday-only scheduled recap through the API so the edit flow starts from a known state.
const createdRecap = await userClient.createScheduledRecap({
title: originalTitle,
days_of_week: MONDAY,
time_of_day: '09:00',
timezone: 'UTC',
time_period: 'last_24h',
channel_mode: 'specific',
channel_ids: [channel.id],
agent_id: agent.id,
is_recurring: true,
});
// # Open the Scheduled tab and launch the edit modal from the recap's Options menu.
const {recapsPage} = await pw.testBrowser.login(user);
await recapsPage.goto(team.name);
await recapsPage.toBeVisible();
await recapsPage.switchToScheduled();
const scheduledRecap = recapsPage.getScheduledRecap(originalTitle);
await scheduledRecap.toBeVisible();
await scheduledRecap.editViaMenu();
// # Update the title on the first step and add Wednesday on the schedule step of the pre-filled modal.
const editModal = recapsPage.createRecapModal;
await editModal.toBeVisible();
await editModal.fillTitle(updatedTitle);
await editModal.clickNext();
await editModal.expectChannelSelectorVisible();
await editModal.clickNext();
await editModal.expectScheduleConfigurationVisible();
await editModal.selectScheduleDay('W');
await editModal.saveChanges();
// * Verify the list reflects the new title and the dropped original title, and shows the added day.
await recapsPage.toBeVisible();
const updatedRecap = recapsPage.getScheduledRecap(updatedTitle);
await updatedRecap.toBeVisible();
await updatedRecap.expectSchedulePattern(/Wed|Wednesday/);
await recapsPage.expectScheduledRecapNotVisible(originalTitle);
// * Verify the persisted scheduled recap carries the updated title and the Monday+Wednesday schedule.
await pw.waitUntil(
async () => {
const scheduledRecaps = await userClient.getScheduledRecaps(0, 60);
const persisted = scheduledRecaps.find((recap) => recap.title === updatedTitle);
return Boolean(persisted) && persisted?.days_of_week === (MONDAY | WEDNESDAY);
},
{timeout: pw.duration.one_min},
);
const scheduledRecaps = await userClient.getScheduledRecaps(0, 60);
expect(scheduledRecaps.some((recap) => recap.title === originalTitle)).toBe(false);
const persisted = scheduledRecaps.find((recap) => recap.title === updatedTitle);
expect(persisted).toBeDefined();
expect(persisted?.days_of_week).toBe(MONDAY | WEDNESDAY);
expect(persisted?.channel_ids).toContain(channel.id);
// * Verify the edit updated the existing recap in place (same id) rather than recreating it, and
// that fields untouched by the edit (time_of_day) survived.
expect(persisted?.id).toBe(createdRecap.id);
expect(persisted?.time_of_day).toBe('09:00');
});
/**
* @objective Verify a user can delete a scheduled recap through the Options menu and confirm dialog
*/
test('deletes a scheduled recap through the options menu', {tag: '@ai_recaps'}, async ({pw}) => {
const recapTitle = `Deletable scheduled recap ${pw.random.id()}`;
const sourceMessage = `Delete scheduled source ${pw.random.id()}`;
// # Initialize the test server state, configure the recap bridge, and seed a channel.
const {adminClient, adminUser, team, user, userClient} = await pw.initSetup();
if (!adminUser) {
throw new Error('Failed to create admin user');
}
const {agent} = await setupRecapBridge(pw, adminClient, {
completions: [],
});
const channel = await createUnreadChannelFixture(
pw,
adminClient,
adminUser.id,
user.id,
team.id,
'Delete scheduled recap channel',
sourceMessage,
);
// # Create a scheduled recap through the API so the UI delete flow has something to remove.
const createdRecap = await userClient.createScheduledRecap({
title: recapTitle,
days_of_week: MONDAY,
time_of_day: '09:00',
timezone: 'UTC',
time_period: 'last_24h',
channel_mode: 'specific',
channel_ids: [channel.id],
agent_id: agent.id,
is_recurring: true,
});
// # Open the Scheduled tab, trigger Delete from the Options menu, and confirm the dialog.
const {recapsPage} = await pw.testBrowser.login(user);
await recapsPage.goto(team.name);
await recapsPage.toBeVisible();
await recapsPage.switchToScheduled();
const scheduledRecap = recapsPage.getScheduledRecap(recapTitle);
await scheduledRecap.toBeVisible();
await scheduledRecap.deleteViaMenu();
await recapsPage.confirmDelete();
// * Verify the recap disappears from the list and the empty state returns.
await recapsPage.expectScheduledRecapNotVisible(recapTitle);
await recapsPage.expectScheduledEmptyState();
// * Verify the scheduled recap is no longer returned by the API.
await pw.waitUntil(
async () => {
const scheduledRecaps = await userClient.getScheduledRecaps(0, 60);
return !scheduledRecaps.some((recap) => recap.id === createdRecap.id);
},
{timeout: pw.duration.one_min},
);
});
/**
* @objective Verify a fresh user with no scheduled recaps sees the scheduled empty-state call to action
*/
test('shows the scheduled empty state for a user with no scheduled recaps', {tag: '@ai_recaps'}, async ({pw}) => {
// # Initialize the test server state and configure the recap bridge so the page renders fully.
const {adminClient, team, user} = await pw.initSetup();
await setupRecapBridge(pw, adminClient, {
completions: [],
});
// # Open the recaps page and switch to the Scheduled tab.
const {recapsPage} = await pw.testBrowser.login(user);
await recapsPage.goto(team.name);
await recapsPage.toBeVisible();
await recapsPage.switchToScheduled();
// * Verify the scheduled empty state heading, description, and create CTA are shown.
await recapsPage.expectScheduledEmptyState();
// * Verify the empty-state CTA is wired up: clicking it opens the create recap modal.
const createRecapModal = await recapsPage.openCreateRecapFromScheduledEmptyState();
await createRecapModal.toBeVisible();
});
/**
* @objective Verify the all-unreads scheduled flow skips channel selection and stores the all_unreads mode
*/
test('creates an all-unreads scheduled recap through the modal', {tag: '@ai_recaps'}, async ({pw}) => {
const recapTitle = `All unreads scheduled recap ${pw.random.id()}`;
const sourceMessage = `All unreads scheduled source ${pw.random.id()}`;
// # Initialize the test server state, configure the recap bridge, and seed an unread channel.
const {adminClient, adminUser, team, user, userClient} = await pw.initSetup();
if (!adminUser) {
throw new Error('Failed to create admin user');
}
const {agent} = await setupRecapBridge(pw, adminClient, {
completions: [],
});
await createUnreadChannelFixture(
pw,
adminClient,
adminUser.id,
user.id,
team.id,
'All unreads scheduled channel',
sourceMessage,
);
// # Open the modal, choose all-unreads mode, and advance directly to the schedule step.
const {recapsPage} = await pw.testBrowser.login(user);
await recapsPage.goto(team.name);
await recapsPage.toBeVisible();
const createRecapModal = await recapsPage.openCreateRecap();
await createRecapModal.fillTitle(recapTitle);
await createRecapModal.selectAllUnreads();
await createRecapModal.clickNext();
// * Verify the all-unreads flow skips the channel selector and lands on schedule configuration.
await createRecapModal.expectChannelSelectorHidden();
await createRecapModal.expectScheduleConfigurationVisible();
await createRecapModal.selectScheduleDay('M');
await createRecapModal.createSchedule();
// * Verify the scheduled recap is listed.
await recapsPage.toBeVisible();
await recapsPage.switchToScheduled();
await recapsPage.getScheduledRecap(recapTitle).toBeVisible();
// * Verify the persisted scheduled recap uses the all_unreads channel mode and is active.
await pw.waitUntil(
async () => {
const scheduledRecaps = await userClient.getScheduledRecaps(0, 60);
return scheduledRecaps.some((recap) => recap.title === recapTitle);
},
{timeout: pw.duration.one_min},
);
const scheduledRecaps = await userClient.getScheduledRecaps(0, 60);
const persisted = scheduledRecaps.find((recap) => recap.title === recapTitle);
expect(persisted).toBeDefined();
expect(persisted?.channel_mode).toBe('all_unreads');
expect(persisted?.agent_id).toBe(agent.id);
expect(persisted?.days_of_week).toBe(MONDAY);
expect(persisted?.enabled).toBe(true);
// * Verify the all_unreads contract is fully pinned: no specific channels are persisted.
expect(persisted?.channel_ids ?? []).toHaveLength(0);
});