2026-07-18 15:57:44 +08:00
package projects
import (
"fmt"
"testing"
2026-07-19 21:46:43 +08:00
"time"
2026-07-18 15:57:44 +08:00
"github.com/glebarez/sqlite"
"github.com/stretchr/testify/require"
"gorm.io/gorm"
2026-07-18 21:58:15 +08:00
"senlinai-agent/backend/internal/models"
2026-07-18 15:57:44 +08:00
)
func TestProjectTagsAreScopedToProject ( t * testing . T ) {
2026-07-18 21:58:15 +08:00
newTestDB ( t )
service := NewService ( )
2026-07-18 15:57:44 +08:00
first , err := service . CreateProject ( 1 , "Alpha" , "" )
require . NoError ( t , err )
second , err := service . CreateProject ( 1 , "Beta" , "" )
require . NoError ( t , err )
_ , err = service . CreateTag ( first . ID , "重要" )
require . NoError ( t , err )
_ , err = service . CreateTag ( second . ID , "重要" )
require . NoError ( t , err )
firstTags , err := service . ListTags ( first . ID )
require . NoError ( t , err )
require . Len ( t , firstTags , 1 )
require . Equal ( t , first . ID , firstTags [ 0 ] . ProjectID )
}
func TestDashboardCountsOnlyRequestedProject ( t * testing . T ) {
database := newTestDB ( t )
2026-07-18 21:58:15 +08:00
service := NewService ( )
2026-07-18 15:57:44 +08:00
first , err := service . CreateProject ( 1 , "Alpha" , "" )
require . NoError ( t , err )
second , err := service . CreateProject ( 1 , "Beta" , "" )
require . NoError ( t , err )
2026-07-18 21:58:15 +08:00
require . NoError ( t , database . Create ( & models . SenlinAgentInboxItem { ProjectID : first . ID , CreatedBy : 1 , SourceType : "text" , Status : "open" } ) . Error )
require . NoError ( t , database . Create ( & models . SenlinAgentInboxItem { ProjectID : second . ID , CreatedBy : 1 , SourceType : "text" , Status : "open" } ) . Error )
require . NoError ( t , database . Create ( & models . SenlinAgentTask { ProjectID : first . ID , CreatedBy : 1 , Title : "A" , Status : "open" } ) . Error )
require . NoError ( t , database . Create ( & models . SenlinAgentTask { ProjectID : second . ID , CreatedBy : 1 , Title : "B" , Status : "open" } ) . Error )
2026-07-18 15:57:44 +08:00
2026-07-18 18:02:29 +08:00
dashboard , err := service . Dashboard ( 1 , first . ID )
2026-07-18 15:57:44 +08:00
require . NoError ( t , err )
require . Equal ( t , int64 ( 1 ) , dashboard . PendingInboxCount )
require . Equal ( t , int64 ( 1 ) , dashboard . OpenTaskCount )
}
2026-07-18 18:02:29 +08:00
func TestDashboardRejectsProjectOwnedByAnotherUser ( t * testing . T ) {
2026-07-18 21:58:15 +08:00
newTestDB ( t )
service := NewService ( )
2026-07-18 18:02:29 +08:00
project , err := service . CreateProject ( 2 , "Beta" , "" )
require . NoError ( t , err )
_ , err = service . Dashboard ( 1 , project . ID )
require . Error ( t , err )
}
2026-07-19 21:46:43 +08:00
func TestWorkspaceMatchesFrontendContract ( t * testing . T ) {
database := newTestDB ( t )
service := NewService ( )
require . NoError ( t , database . Create ( & models . SenlinAgentUser { Email : "david@example.com" , DisplayName : "David" , PasswordHash : "hash" } ) . Error )
project , err := service . CreateProject ( 1 , "Project A1" , "Client strategy workspace" )
require . NoError ( t , err )
other , err := service . CreateProject ( 1 , "Project A2" , "" )
require . NoError ( t , err )
nextRun := time . Date ( 2026 , 7 , 20 , 9 , 0 , 0 , 0 , time . UTC )
require . NoError ( t , database . Create ( & models . SenlinAgentTag { ProjectID : project . ID , Name : "UI" } ) . Error )
require . NoError ( t , database . Create ( & models . SenlinAgentTag { ProjectID : project . ID , Name : "knowledge" } ) . Error )
require . NoError ( t , database . Create ( & models . SenlinAgentInboxItem { ProjectID : project . ID , CreatedBy : 1 , SourceType : "Manual" , Title : "Collect notes" , Body : "Turn pasted research into tasks." , Status : "open" } ) . Error )
require . NoError ( t , database . Create ( & models . SenlinAgentInboxItem { ProjectID : other . ID , CreatedBy : 1 , SourceType : "Manual" , Title : "Other notes" , Body : "Hidden" , Status : "open" } ) . Error )
require . NoError ( t , database . Create ( & models . SenlinAgentTask { ProjectID : project . ID , CreatedBy : 1 , Title : "Confirm IA" , Description : "Review channel layout." , Status : "open" , DueAt : & nextRun } ) . Error )
require . NoError ( t , database . Create ( & models . SenlinAgentTask { ProjectID : project . ID , CreatedBy : 1 , Title : "Archive notes" , Description : "Move outdated notes." , Status : "done" } ) . Error )
require . NoError ( t , database . Create ( & models . SenlinAgentAISession { ProjectID : project . ID , CreatedBy : 1 , Title : "UI prototype critique" , Context : "Discuss channel behavior." } ) . Error )
require . NoError ( t , database . Create ( & models . SenlinAgentNote { ProjectID : project . ID , CreatedBy : 1 , Title : "Workbench principles" , Markdown : "# Notes" } ) . Error )
require . NoError ( t , database . Create ( & models . SenlinAgentSource { ProjectID : project . ID , CreatedBy : 1 , Kind : "link" , Title : "Reference board" , URL : "https://example.com" } ) . Error )
require . NoError ( t , database . Create ( & models . SenlinAgentProjectChannel { ProjectID : project . ID , Title : "Roadmap Board" , Icon : "link" , URL : "https://example.com/roadmap-a1" , SortOrder : 7 } ) . Error )
require . NoError ( t , database . Create ( & models . SenlinAgentProjectChannel { ProjectID : other . ID , Title : "Other Board" , Icon : "link" , URL : "https://example.com/other" , SortOrder : 7 } ) . Error )
require . NoError ( t , database . Create ( & models . SenlinAgentCronPlan { ProjectID : project . ID , CreatedBy : 1 , Title : "Weekly inbox review reminder" , Schedule : "0 9 * * 1" , NextRunAt : & nextRun , Enabled : true , LastResult : "Not run yet" } ) . Error )
require . NoError ( t , database . Create ( & models . SenlinAgentCronPlan { ProjectID : other . ID , CreatedBy : 1 , Title : "Other cron" , Schedule : "0 9 * * 2" , Enabled : true } ) . Error )
workspace , err := service . Workspace ( 1 , project . ID )
require . NoError ( t , err )
require . Equal ( t , project . ID , workspace . Project . ID )
require . Equal ( t , "Project A1" , workspace . Project . Name )
require . Equal ( t , "A1" , workspace . Project . Initials )
require . Equal ( t , int64 ( 1 ) , workspace . Project . UnreadCount )
require . Equal ( t , [ ] string { "all" , "UI" , "knowledge" } , workspace . Tags )
require . Len ( t , workspace . Channels , 7 )
require . Equal ( t , "overview" , workspace . Channels [ 0 ] . Type )
require . Equal ( t , "inbox" , workspace . Channels [ 1 ] . Type )
require . Equal ( t , int64 ( 1 ) , * workspace . Channels [ 1 ] . Count )
require . Equal ( t , "custom_link" , workspace . Channels [ 6 ] . Type )
require . Equal ( t , "Roadmap Board" , workspace . Channels [ 6 ] . Title )
require . Equal ( t , "https://example.com/roadmap-a1" , workspace . Channels [ 6 ] . URL )
require . Len ( t , workspace . Inbox , 1 )
require . Equal ( t , "Manual" , workspace . Inbox [ 0 ] . Source )
require . Equal ( t , "Turn pasted research into tasks." , workspace . Inbox [ 0 ] . Summary )
require . Len ( t , workspace . Tasks , 2 )
require . False ( t , workspace . Tasks [ 0 ] . Completed )
require . Equal ( t , "David" , workspace . Tasks [ 0 ] . Owner )
require . Len ( t , workspace . AISessions , 1 )
require . Len ( t , workspace . RecentSessions , 1 )
require . Len ( t , workspace . NotesSources , 2 )
require . Equal ( t , "note" , workspace . NotesSources [ 0 ] . Kind )
require . Equal ( t , "link" , workspace . NotesSources [ 1 ] . Kind )
require . Len ( t , workspace . CronPlans , 1 )
require . True ( t , workspace . CronPlans [ 0 ] . Enabled )
require . Equal ( t , "David" , workspace . CronPlans [ 0 ] . Owner )
}
func TestWorkspaceRejectsProjectOwnedByAnotherUser ( t * testing . T ) {
newTestDB ( t )
service := NewService ( )
project , err := service . CreateProject ( 2 , "Beta" , "" )
require . NoError ( t , err )
_ , err = service . Workspace ( 1 , project . ID )
require . Error ( t , err )
}
2026-07-18 15:57:44 +08:00
func newTestDB ( t * testing . T ) * gorm . DB {
t . Helper ( )
database , err := gorm . Open ( sqlite . Open ( fmt . Sprintf ( "file:%s?mode=memory&cache=shared" , t . Name ( ) ) ) , & gorm . Config { } )
require . NoError ( t , err )
2026-07-18 21:58:15 +08:00
require . NoError ( t , models . AutoMigrate ( database ) )
models . DBService = database
2026-07-18 15:57:44 +08:00
return database
}