mirror of
https://github.com/hanzoai/team.git
synced 2026-09-30 01:57:35 +00:00
The server module named github.com/hanzoai/team/server/v8 — a path that resolves to the retired workspace's repository, not this one — while server/public still published github.com/mattermost/mattermost/server/public. Both are github.com/hanzoteam/server/... now, which is where this code actually lives. That second half is what made the binary unbootable. Upstream's public path stayed claimed by upstream, mattermost-plugin-ai imported it, and the build linked public/model twice; public/plugin's gob.Register then ran twice under one name and panicked at init, before main. Reverting the rename traded the brand for a working binary. Rebranding the plugin alongside it needs no such trade: the agents fork is github.com/hanzoteam/agents/v2 against our public module, so no module named mattermost/mattermost is in the graph at all. Measured: `go list -deps ./cmd/mattermost` names exactly one public/model, and it is ours.
572 lines
21 KiB
Go
572 lines
21 KiB
Go
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
// See LICENSE.txt for license information.
|
|
|
|
package app
|
|
|
|
import (
|
|
"os"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/hanzoteam/server/server/public/model"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// TestScheduledRecap_OverLimitCanManageExisting verifies ENF-07: Users over limit
|
|
// can still manage their existing scheduled recaps. Limits only block creation,
|
|
// not view/edit/delete operations. This is "grandfathering" behavior.
|
|
func TestScheduledRecap_OverLimitCanManageExisting(t *testing.T) {
|
|
os.Setenv("MM_FEATUREFLAGS_ENABLEAIRECAPS", "true")
|
|
defer os.Unsetenv("MM_FEATUREFLAGS_ENABLEAIRECAPS")
|
|
|
|
th := Setup(t).InitBasic(t)
|
|
|
|
// Set a very restrictive limit (1 scheduled recap max)
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
cfg.AIRecapSettings.EnforceScheduledRecaps = model.NewPointer(true)
|
|
cfg.AIRecapSettings.DefaultLimits.MaxScheduledRecaps = model.NewPointer(1)
|
|
})
|
|
|
|
// Create a scheduled recap directly in the store (bypassing API limits)
|
|
// to simulate user who already has scheduled recaps
|
|
scheduledRecap := &model.ScheduledRecap{
|
|
Id: model.NewId(),
|
|
UserId: th.BasicUser.Id,
|
|
Title: "Existing Recap",
|
|
DaysOfWeek: model.EveryDay, // Run every day
|
|
TimeOfDay: "09:00",
|
|
TimePeriod: model.TimePeriodLast24h,
|
|
ChannelMode: model.ChannelModeSpecific,
|
|
ChannelIds: []string{th.BasicChannel.Id},
|
|
AgentId: "test-agent",
|
|
Timezone: "America/New_York",
|
|
IsRecurring: true,
|
|
Enabled: true,
|
|
CreateAt: model.GetMillis(),
|
|
UpdateAt: model.GetMillis(),
|
|
}
|
|
|
|
// Compute initial NextRunAt
|
|
nextRunAt, err := scheduledRecap.ComputeNextRunAt(time.Now())
|
|
require.NoError(t, err)
|
|
scheduledRecap.NextRunAt = nextRunAt
|
|
|
|
savedRecap, saveErr := th.App.Srv().Store().ScheduledRecap().Save(scheduledRecap)
|
|
require.NoError(t, saveErr)
|
|
require.NotNil(t, savedRecap)
|
|
|
|
// Create a second scheduled recap to put user at/over limit
|
|
secondRecap := &model.ScheduledRecap{
|
|
Id: model.NewId(),
|
|
UserId: th.BasicUser.Id,
|
|
Title: "Second Recap",
|
|
DaysOfWeek: model.Monday, // Run on Mondays
|
|
TimeOfDay: "10:30",
|
|
TimePeriod: model.TimePeriodLast24h,
|
|
ChannelMode: model.ChannelModeSpecific,
|
|
ChannelIds: []string{th.BasicChannel.Id},
|
|
AgentId: "test-agent",
|
|
Timezone: "America/New_York",
|
|
IsRecurring: true,
|
|
Enabled: true,
|
|
CreateAt: model.GetMillis(),
|
|
UpdateAt: model.GetMillis(),
|
|
}
|
|
nextRunAt2, err := secondRecap.ComputeNextRunAt(time.Now())
|
|
require.NoError(t, err)
|
|
secondRecap.NextRunAt = nextRunAt2
|
|
|
|
_, saveErr = th.App.Srv().Store().ScheduledRecap().Save(secondRecap)
|
|
require.NoError(t, saveErr)
|
|
|
|
// User now has 2 scheduled recaps but limit is 1 - they are "over limit"
|
|
|
|
ctx := th.Context.WithSession(&model.Session{UserId: th.BasicUser.Id})
|
|
|
|
t.Run("over-limit user can view existing scheduled recap", func(t *testing.T) {
|
|
// ENF-07: Get operations should NOT check limits
|
|
fetchedRecap, getErr := th.App.GetScheduledRecap(ctx, savedRecap.Id)
|
|
require.Nil(t, getErr, "GetScheduledRecap should succeed regardless of limits")
|
|
require.NotNil(t, fetchedRecap)
|
|
assert.Equal(t, savedRecap.Id, fetchedRecap.Id)
|
|
assert.Equal(t, savedRecap.Title, fetchedRecap.Title)
|
|
})
|
|
|
|
t.Run("over-limit user can list existing scheduled recaps", func(t *testing.T) {
|
|
// ENF-07: List operations should NOT check limits
|
|
recaps, listErr := th.App.GetScheduledRecapsForUser(ctx, 0, 10)
|
|
require.Nil(t, listErr, "GetScheduledRecapsForUser should succeed regardless of limits")
|
|
require.NotNil(t, recaps)
|
|
assert.Len(t, recaps, 2, "Should return all user's scheduled recaps")
|
|
})
|
|
|
|
t.Run("over-limit user can update existing scheduled recap", func(t *testing.T) {
|
|
// ENF-07: Update operations should NOT check limits
|
|
savedRecap.Title = "Updated Title By Over-Limit User"
|
|
savedRecap.TimeOfDay = "14:00"
|
|
|
|
updatedRecap, updateErr := th.App.UpdateScheduledRecap(ctx, savedRecap)
|
|
require.Nil(t, updateErr, "UpdateScheduledRecap should succeed regardless of limits")
|
|
require.NotNil(t, updatedRecap)
|
|
assert.Equal(t, "Updated Title By Over-Limit User", updatedRecap.Title)
|
|
assert.Equal(t, "14:00", updatedRecap.TimeOfDay)
|
|
})
|
|
|
|
t.Run("over-limit user can pause existing scheduled recap", func(t *testing.T) {
|
|
// ENF-07: Pause operations should NOT check limits
|
|
pausedRecap, pauseErr := th.App.PauseScheduledRecap(ctx, savedRecap.Id)
|
|
require.Nil(t, pauseErr, "PauseScheduledRecap should succeed regardless of limits")
|
|
require.NotNil(t, pausedRecap)
|
|
assert.False(t, pausedRecap.Enabled)
|
|
})
|
|
|
|
t.Run("over-limit user can resume existing scheduled recap", func(t *testing.T) {
|
|
// ENF-07: Resume operations should NOT check limits
|
|
resumedRecap, resumeErr := th.App.ResumeScheduledRecap(ctx, savedRecap.Id)
|
|
require.Nil(t, resumeErr, "ResumeScheduledRecap should succeed regardless of limits")
|
|
require.NotNil(t, resumedRecap)
|
|
assert.True(t, resumedRecap.Enabled)
|
|
})
|
|
|
|
t.Run("over-limit user can delete existing scheduled recap", func(t *testing.T) {
|
|
// ENF-07: Delete operations should NOT check limits
|
|
// Deleting allows user to get back under limit
|
|
deleteErr := th.App.DeleteScheduledRecap(ctx, savedRecap.Id)
|
|
require.Nil(t, deleteErr, "DeleteScheduledRecap should succeed regardless of limits")
|
|
// Note: Soft delete - record still exists but has DeleteAt set
|
|
})
|
|
}
|
|
|
|
// TestRecap_OverLimitCanManageExisting verifies ENF-07: Users over limit
|
|
// can still manage their existing recaps. Limits only block creation,
|
|
// not view/list/delete operations.
|
|
func TestRecap_OverLimitCanManageExisting(t *testing.T) {
|
|
os.Setenv("MM_FEATUREFLAGS_ENABLEAIRECAPS", "true")
|
|
defer os.Unsetenv("MM_FEATUREFLAGS_ENABLEAIRECAPS")
|
|
|
|
th := Setup(t).InitBasic(t)
|
|
|
|
// Set restrictive limits (doesn't matter - management ops ignore limits)
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
cfg.AIRecapSettings.EnforceRecapsPerDay = model.NewPointer(true)
|
|
cfg.AIRecapSettings.DefaultLimits.MaxRecapsPerDay = model.NewPointer(1)
|
|
cfg.AIRecapSettings.EnforceCooldown = model.NewPointer(true)
|
|
cfg.AIRecapSettings.DefaultLimits.CooldownMinutes = model.NewPointer(999)
|
|
})
|
|
|
|
// Create recap directly in store (bypassing API limits)
|
|
recap := &model.Recap{
|
|
Id: model.NewId(),
|
|
UserId: th.BasicUser.Id,
|
|
Title: "Existing Recap",
|
|
CreateAt: model.GetMillis(),
|
|
UpdateAt: model.GetMillis(),
|
|
DeleteAt: 0,
|
|
ReadAt: 0,
|
|
TotalMessageCount: 25,
|
|
Status: model.RecapStatusCompleted,
|
|
}
|
|
|
|
savedRecap, saveErr := th.App.Srv().Store().Recap().SaveRecap(recap)
|
|
require.NoError(t, saveErr)
|
|
require.NotNil(t, savedRecap)
|
|
|
|
// Create recap channel for complete data
|
|
recapChannel := &model.RecapChannel{
|
|
Id: model.NewId(),
|
|
RecapId: recap.Id,
|
|
ChannelId: th.BasicChannel.Id,
|
|
ChannelName: th.BasicChannel.DisplayName,
|
|
Highlights: []string{"Test highlight 1", "Test highlight 2"},
|
|
ActionItems: []string{"Action item 1"},
|
|
SourcePostIds: []string{model.NewId(), model.NewId()},
|
|
CreateAt: model.GetMillis(),
|
|
}
|
|
err := th.App.Srv().Store().Recap().SaveRecapChannel(recapChannel)
|
|
require.NoError(t, err)
|
|
|
|
ctx := th.Context.WithSession(&model.Session{UserId: th.BasicUser.Id})
|
|
|
|
t.Run("over-limit user can view existing recap", func(t *testing.T) {
|
|
// ENF-07: Get operations should NOT check limits
|
|
fetchedRecap, getErr := th.App.GetRecap(ctx, recap.Id)
|
|
require.Nil(t, getErr, "GetRecap should succeed regardless of limits")
|
|
require.NotNil(t, fetchedRecap)
|
|
assert.Equal(t, recap.Id, fetchedRecap.Id)
|
|
assert.Equal(t, recap.Title, fetchedRecap.Title)
|
|
assert.Len(t, fetchedRecap.Channels, 1)
|
|
})
|
|
|
|
t.Run("over-limit user can list existing recaps", func(t *testing.T) {
|
|
// ENF-07: List operations should NOT check limits
|
|
recaps, listErr := th.App.GetRecapsForUser(ctx, 0, 10)
|
|
require.Nil(t, listErr, "GetRecapsForUser should succeed regardless of limits")
|
|
require.NotNil(t, recaps)
|
|
assert.GreaterOrEqual(t, len(recaps), 1)
|
|
})
|
|
|
|
t.Run("over-limit user can mark recap as read", func(t *testing.T) {
|
|
// ENF-07: Mark read operations should NOT check limits
|
|
readRecap, markErr := th.App.MarkRecapAsRead(ctx, savedRecap)
|
|
require.Nil(t, markErr, "MarkRecapAsRead should succeed regardless of limits")
|
|
require.NotNil(t, readRecap)
|
|
assert.Greater(t, readRecap.ReadAt, int64(0))
|
|
})
|
|
|
|
t.Run("over-limit user can delete existing recap", func(t *testing.T) {
|
|
// ENF-07: Delete operations should NOT check limits
|
|
// Deleting allows user to get back under limit
|
|
deleteErr := th.App.DeleteRecap(ctx, recap.Id)
|
|
require.Nil(t, deleteErr, "DeleteRecap should succeed regardless of limits")
|
|
})
|
|
}
|
|
|
|
// TestScheduledRecap_CreateBlockedWhenOverLimit verifies that while management
|
|
// operations succeed for over-limit users, creation IS still blocked.
|
|
// This confirms limits work correctly for creation while allowing management.
|
|
func TestScheduledRecap_CreateBlockedWhenOverLimit(t *testing.T) {
|
|
os.Setenv("MM_FEATUREFLAGS_ENABLEAIRECAPS", "true")
|
|
defer os.Unsetenv("MM_FEATUREFLAGS_ENABLEAIRECAPS")
|
|
|
|
th := Setup(t).InitBasic(t)
|
|
|
|
// Set limit to 1 scheduled recap
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
cfg.AIRecapSettings.EnforceScheduledRecaps = model.NewPointer(true)
|
|
cfg.AIRecapSettings.DefaultLimits.MaxScheduledRecaps = model.NewPointer(1)
|
|
})
|
|
|
|
ctx := th.Context.WithSession(&model.Session{UserId: th.BasicUser.Id})
|
|
|
|
// Create first scheduled recap (should succeed)
|
|
firstRecap := &model.ScheduledRecap{
|
|
Title: "First Recap",
|
|
DaysOfWeek: model.EveryDay,
|
|
TimeOfDay: "09:00",
|
|
TimePeriod: model.TimePeriodLast24h,
|
|
ChannelMode: model.ChannelModeSpecific,
|
|
ChannelIds: []string{th.BasicChannel.Id},
|
|
AgentId: "test-agent",
|
|
Timezone: "America/New_York",
|
|
IsRecurring: true,
|
|
Enabled: true,
|
|
}
|
|
|
|
createdRecap, createErr := th.App.CreateScheduledRecap(ctx, firstRecap)
|
|
require.Nil(t, createErr, "First scheduled recap should be created successfully")
|
|
require.NotNil(t, createdRecap)
|
|
|
|
// Try to create second scheduled recap (should fail - over limit)
|
|
secondRecap := &model.ScheduledRecap{
|
|
Title: "Second Recap",
|
|
DaysOfWeek: model.EveryDay,
|
|
TimeOfDay: "10:00",
|
|
TimePeriod: model.TimePeriodLast24h,
|
|
ChannelMode: model.ChannelModeSpecific,
|
|
ChannelIds: []string{th.BasicChannel.Id},
|
|
AgentId: "test-agent",
|
|
Timezone: "America/New_York",
|
|
IsRecurring: true,
|
|
Enabled: true,
|
|
}
|
|
|
|
_, createErr = th.App.CreateScheduledRecap(ctx, secondRecap)
|
|
require.NotNil(t, createErr, "Second scheduled recap should be blocked by limit")
|
|
assert.Equal(t, "app.scheduled_recap.max_scheduled_reached.app_error", createErr.Id)
|
|
|
|
// But user can still update and delete their existing recap
|
|
createdRecap.Title = "Updated Title"
|
|
updatedRecap, updateErr := th.App.UpdateScheduledRecap(ctx, createdRecap)
|
|
require.Nil(t, updateErr, "Update should succeed for over-limit user")
|
|
assert.Equal(t, "Updated Title", updatedRecap.Title)
|
|
}
|
|
|
|
func TestScheduledRecapCreateAndUpdateState(t *testing.T) {
|
|
os.Setenv("MM_FEATUREFLAGS_ENABLEAIRECAPS", "true")
|
|
defer os.Unsetenv("MM_FEATUREFLAGS_ENABLEAIRECAPS")
|
|
|
|
th := Setup(t).InitBasic(t)
|
|
ctx := th.Context.WithSession(&model.Session{UserId: th.BasicUser.Id})
|
|
|
|
recap := &model.ScheduledRecap{
|
|
Title: "Default Enabled Recap",
|
|
DaysOfWeek: model.EveryDay,
|
|
TimeOfDay: "09:00",
|
|
TimePeriod: model.TimePeriodLast24h,
|
|
ChannelMode: model.ChannelModeSpecific,
|
|
ChannelIds: []string{th.BasicChannel.Id},
|
|
AgentId: "test-agent",
|
|
Timezone: "America/New_York",
|
|
IsRecurring: true,
|
|
}
|
|
|
|
createdRecap, createErr := th.App.CreateScheduledRecap(ctx, recap)
|
|
require.Nil(t, createErr)
|
|
require.NotNil(t, createdRecap)
|
|
assert.True(t, createdRecap.Enabled)
|
|
|
|
lastRunAt := model.GetMillis()
|
|
nextRunAt := lastRunAt + int64(time.Hour/time.Millisecond)
|
|
require.NoError(t, th.App.Srv().Store().ScheduledRecap().MarkExecuted(createdRecap.Id, lastRunAt, nextRunAt))
|
|
|
|
staleUpdate := &model.ScheduledRecap{
|
|
Id: createdRecap.Id,
|
|
Title: "Updated Without State Fields",
|
|
DaysOfWeek: model.Monday,
|
|
TimeOfDay: "10:00",
|
|
TimePeriod: model.TimePeriodLastWeek,
|
|
ChannelMode: model.ChannelModeSpecific,
|
|
ChannelIds: []string{th.BasicChannel.Id},
|
|
AgentId: "test-agent",
|
|
Timezone: "America/New_York",
|
|
IsRecurring: true,
|
|
}
|
|
|
|
updatedRecap, updateErr := th.App.UpdateScheduledRecap(ctx, staleUpdate)
|
|
require.Nil(t, updateErr)
|
|
require.NotNil(t, updatedRecap)
|
|
assert.Equal(t, "Updated Without State Fields", updatedRecap.Title)
|
|
assert.True(t, updatedRecap.Enabled)
|
|
assert.Equal(t, lastRunAt, updatedRecap.LastRunAt)
|
|
assert.Equal(t, 1, updatedRecap.RunCount)
|
|
}
|
|
|
|
func TestCreateRecapFromScheduleAllUnreads(t *testing.T) {
|
|
os.Setenv("MM_FEATUREFLAGS_ENABLEAIRECAPS", "true")
|
|
defer os.Unsetenv("MM_FEATUREFLAGS_ENABLEAIRECAPS")
|
|
|
|
th := Setup(t).InitBasic(t)
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
cfg.AIRecapSettings.EnforceRecapsPerDay = model.NewPointer(false)
|
|
cfg.AIRecapSettings.EnforceChannelsPerRecap = model.NewPointer(true)
|
|
cfg.AIRecapSettings.DefaultLimits.MaxChannelsPerRecap = model.NewPointer(10)
|
|
})
|
|
|
|
th.AddUserToChannel(t, th.BasicUser2, th.BasicChannel)
|
|
post := &model.Post{
|
|
UserId: th.BasicUser2.Id,
|
|
ChannelId: th.BasicChannel.Id,
|
|
Message: "unread for scheduled recap",
|
|
CreateAt: model.GetMillis(),
|
|
}
|
|
_, _, appErr := th.App.CreatePost(th.Context, post, th.BasicChannel, model.CreatePostFlags{})
|
|
require.Nil(t, appErr)
|
|
|
|
scheduledRecap := &model.ScheduledRecap{
|
|
Id: model.NewId(),
|
|
UserId: th.BasicUser.Id,
|
|
Title: "All Unreads",
|
|
DaysOfWeek: model.EveryDay,
|
|
TimeOfDay: "09:00",
|
|
TimePeriod: model.TimePeriodLastWeek,
|
|
ChannelMode: model.ChannelModeAllUnreads,
|
|
CustomInstructions: "Focus on launch risks",
|
|
AgentId: "test-agent",
|
|
Timezone: "America/New_York",
|
|
IsRecurring: true,
|
|
Enabled: true,
|
|
CreateAt: model.GetMillis(),
|
|
UpdateAt: model.GetMillis(),
|
|
}
|
|
|
|
recap, createErr := th.App.CreateRecapFromSchedule(th.Context, scheduledRecap)
|
|
require.Nil(t, createErr)
|
|
require.NotNil(t, recap)
|
|
assert.Equal(t, scheduledRecap.Id, recap.ScheduledRecapId)
|
|
assert.Equal(t, th.BasicUser.Id, recap.UserId)
|
|
|
|
jobs, err := th.App.Srv().Store().Job().GetAllByTypeAndStatus(th.Context, model.JobTypeRecap, model.JobStatusPending)
|
|
require.NoError(t, err)
|
|
|
|
var recapJob *model.Job
|
|
for _, job := range jobs {
|
|
if job.Data["recap_id"] == recap.Id {
|
|
recapJob = job
|
|
break
|
|
}
|
|
}
|
|
require.NotNil(t, recapJob)
|
|
assert.Equal(t, model.TimePeriodLastWeek, recapJob.Data["time_period"])
|
|
assert.Equal(t, "Focus on launch risks", recapJob.Data["custom_instructions"])
|
|
}
|
|
|
|
func TestCreateScheduledRecapMasterToggleDisabled(t *testing.T) {
|
|
os.Setenv("MM_FEATUREFLAGS_ENABLEAIRECAPS", "true")
|
|
defer os.Unsetenv("MM_FEATUREFLAGS_ENABLEAIRECAPS")
|
|
|
|
th := Setup(t).InitBasic(t)
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
cfg.FeatureFlags.EnableAIRecaps = true
|
|
cfg.AIRecapSettings.Enable = model.NewPointer(false)
|
|
})
|
|
|
|
ctx := th.Context.WithSession(&model.Session{UserId: th.BasicUser.Id})
|
|
recap := &model.ScheduledRecap{
|
|
Title: "Disabled Recap",
|
|
DaysOfWeek: model.EveryDay,
|
|
TimeOfDay: "09:00",
|
|
TimePeriod: model.TimePeriodLast24h,
|
|
ChannelMode: model.ChannelModeSpecific,
|
|
ChannelIds: []string{th.BasicChannel.Id},
|
|
AgentId: "test-agent",
|
|
Timezone: "America/New_York",
|
|
IsRecurring: true,
|
|
}
|
|
|
|
createdRecap, createErr := th.App.CreateScheduledRecap(ctx, recap)
|
|
require.NotNil(t, createErr)
|
|
require.Nil(t, createdRecap)
|
|
assert.Equal(t, "api.recap.disabled.app_error", createErr.Id)
|
|
|
|
scheduledRecap := &model.ScheduledRecap{
|
|
Id: model.NewId(),
|
|
UserId: th.BasicUser.Id,
|
|
Title: "Disabled Execution",
|
|
DaysOfWeek: model.EveryDay,
|
|
TimeOfDay: "09:00",
|
|
TimePeriod: model.TimePeriodLast24h,
|
|
ChannelMode: model.ChannelModeSpecific,
|
|
ChannelIds: []string{th.BasicChannel.Id},
|
|
AgentId: "test-agent",
|
|
Timezone: "America/New_York",
|
|
IsRecurring: true,
|
|
Enabled: true,
|
|
}
|
|
createdFromSchedule, appErr := th.App.CreateRecapFromSchedule(th.Context, scheduledRecap)
|
|
require.NotNil(t, appErr)
|
|
require.Nil(t, createdFromSchedule)
|
|
assert.Equal(t, "api.recap.disabled.app_error", appErr.Id)
|
|
}
|
|
|
|
func TestCreateScheduledRecapFeatureFlagDisabled(t *testing.T) {
|
|
th := Setup(t).InitBasic(t)
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
cfg.FeatureFlags.EnableAIRecaps = false
|
|
cfg.AIRecapSettings.Enable = model.NewPointer(true)
|
|
})
|
|
|
|
ctx := th.Context.WithSession(&model.Session{UserId: th.BasicUser.Id})
|
|
recap := &model.ScheduledRecap{
|
|
Title: "Disabled Recap",
|
|
DaysOfWeek: model.EveryDay,
|
|
TimeOfDay: "09:00",
|
|
TimePeriod: model.TimePeriodLast24h,
|
|
ChannelMode: model.ChannelModeSpecific,
|
|
ChannelIds: []string{th.BasicChannel.Id},
|
|
AgentId: "test-agent",
|
|
Timezone: "America/New_York",
|
|
IsRecurring: true,
|
|
}
|
|
|
|
createdRecap, createErr := th.App.CreateScheduledRecap(ctx, recap)
|
|
require.NotNil(t, createErr)
|
|
require.Nil(t, createdRecap)
|
|
assert.Equal(t, "api.recap.disabled.app_error", createErr.Id)
|
|
|
|
scheduledRecap := &model.ScheduledRecap{
|
|
Id: model.NewId(),
|
|
UserId: th.BasicUser.Id,
|
|
Title: "Disabled Execution",
|
|
DaysOfWeek: model.EveryDay,
|
|
TimeOfDay: "09:00",
|
|
TimePeriod: model.TimePeriodLast24h,
|
|
ChannelMode: model.ChannelModeSpecific,
|
|
ChannelIds: []string{th.BasicChannel.Id},
|
|
AgentId: "test-agent",
|
|
Timezone: "America/New_York",
|
|
IsRecurring: true,
|
|
Enabled: true,
|
|
}
|
|
createdFromSchedule, appErr := th.App.CreateRecapFromSchedule(th.Context, scheduledRecap)
|
|
require.NotNil(t, appErr)
|
|
require.Nil(t, createdFromSchedule)
|
|
assert.Equal(t, "api.recap.disabled.app_error", appErr.Id)
|
|
}
|
|
|
|
func TestScheduledRecapChannelValidationAndDeduplication(t *testing.T) {
|
|
os.Setenv("MM_FEATUREFLAGS_ENABLEAIRECAPS", "true")
|
|
defer os.Unsetenv("MM_FEATUREFLAGS_ENABLEAIRECAPS")
|
|
|
|
th := Setup(t).InitBasic(t)
|
|
ctx := th.Context.WithSession(&model.Session{UserId: th.BasicUser.Id})
|
|
|
|
t.Run("create rejects channel without read permission", func(t *testing.T) {
|
|
privateChannel := th.CreatePrivateChannel(t, th.BasicTeam)
|
|
_ = th.App.RemoveUserFromChannel(th.Context, th.BasicUser.Id, "", privateChannel)
|
|
th.AddUserToChannel(t, th.BasicUser2, privateChannel)
|
|
|
|
recap := &model.ScheduledRecap{
|
|
Title: "Restricted Recap",
|
|
DaysOfWeek: model.EveryDay,
|
|
TimeOfDay: "09:00",
|
|
TimePeriod: model.TimePeriodLast24h,
|
|
ChannelMode: model.ChannelModeSpecific,
|
|
ChannelIds: []string{privateChannel.Id},
|
|
AgentId: "test-agent",
|
|
Timezone: "America/New_York",
|
|
IsRecurring: true,
|
|
Enabled: true,
|
|
}
|
|
|
|
createdRecap, createErr := th.App.CreateScheduledRecap(ctx, recap)
|
|
require.NotNil(t, createErr)
|
|
require.Nil(t, createdRecap)
|
|
assert.Equal(t, "app.recap.permission_denied", createErr.Id)
|
|
})
|
|
|
|
t.Run("update rejects channel without read permission", func(t *testing.T) {
|
|
recap := &model.ScheduledRecap{
|
|
Title: "Valid Recap",
|
|
DaysOfWeek: model.EveryDay,
|
|
TimeOfDay: "09:00",
|
|
TimePeriod: model.TimePeriodLast24h,
|
|
ChannelMode: model.ChannelModeSpecific,
|
|
ChannelIds: []string{th.BasicChannel.Id},
|
|
AgentId: "test-agent",
|
|
Timezone: "America/New_York",
|
|
IsRecurring: true,
|
|
Enabled: true,
|
|
}
|
|
|
|
createdRecap, createErr := th.App.CreateScheduledRecap(ctx, recap)
|
|
require.Nil(t, createErr)
|
|
require.NotNil(t, createdRecap)
|
|
|
|
privateChannel := th.CreatePrivateChannel(t, th.BasicTeam)
|
|
_ = th.App.RemoveUserFromChannel(th.Context, th.BasicUser.Id, "", privateChannel)
|
|
th.AddUserToChannel(t, th.BasicUser2, privateChannel)
|
|
|
|
createdRecap.ChannelIds = []string{privateChannel.Id}
|
|
updatedRecap, updateErr := th.App.UpdateScheduledRecap(ctx, createdRecap)
|
|
require.NotNil(t, updateErr)
|
|
require.Nil(t, updatedRecap)
|
|
assert.Equal(t, "app.recap.permission_denied", updateErr.Id)
|
|
})
|
|
|
|
t.Run("create deduplicates repeated channel ids before limit checks", func(t *testing.T) {
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
cfg.AIRecapSettings.EnforceChannelsPerRecap = model.NewPointer(true)
|
|
cfg.AIRecapSettings.DefaultLimits.MaxChannelsPerRecap = model.NewPointer(1)
|
|
})
|
|
|
|
recap := &model.ScheduledRecap{
|
|
Title: "Deduped Recap",
|
|
DaysOfWeek: model.EveryDay,
|
|
TimeOfDay: "09:00",
|
|
TimePeriod: model.TimePeriodLast24h,
|
|
ChannelMode: model.ChannelModeSpecific,
|
|
ChannelIds: []string{th.BasicChannel.Id, th.BasicChannel.Id},
|
|
AgentId: "test-agent",
|
|
Timezone: "America/New_York",
|
|
IsRecurring: true,
|
|
Enabled: true,
|
|
}
|
|
|
|
createdRecap, createErr := th.App.CreateScheduledRecap(ctx, recap)
|
|
require.Nil(t, createErr)
|
|
require.NotNil(t, createdRecap)
|
|
assert.Equal(t, model.StringArray{th.BasicChannel.Id}, createdRecap.ChannelIds)
|
|
})
|
|
}
|