mirror of
https://github.com/hanzoai/team.git
synced 2026-10-30 14:49:35 +00:00
401 lines
16 KiB
TypeScript
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);
|
|
});
|