@@ -801,3 +801,72 @@ func (s *TaskTestSuite) TestGetTasksByLabel() {
801801 // All labels are preloaded (not just the filtered one)
802802 s .Require ().Len (result [1 ].Labels , 2 )
803803}
804+
805+ func (s * TaskTestSuite ) TestSearchTasksByTitle () {
806+ ctx := context .Background ()
807+
808+ now := time .Now ().UTC ()
809+ due1 := now .Add (24 * time .Hour )
810+ due2 := now .Add (48 * time .Hour )
811+
812+ groceries := & models.Task {
813+ Title : "Buy groceries" ,
814+ CreatedBy : s .testUser .ID ,
815+ NextDueDate : & due2 ,
816+ IsActive : true ,
817+ Frequency : models.Frequency {Type : models .RepeatOnce },
818+ }
819+ s .Require ().NoError (s .DB .Create (groceries ).Error )
820+
821+ grocerySort := & models.Task {
822+ Title : "Sort Groceries in pantry" ,
823+ CreatedBy : s .testUser .ID ,
824+ NextDueDate : & due1 ,
825+ IsActive : true ,
826+ Frequency : models.Frequency {Type : models .RepeatOnce },
827+ }
828+ s .Require ().NoError (s .DB .Create (grocerySort ).Error )
829+
830+ unrelated := & models.Task {
831+ Title : "Walk the dog" ,
832+ CreatedBy : s .testUser .ID ,
833+ NextDueDate : & due1 ,
834+ IsActive : true ,
835+ Frequency : models.Frequency {Type : models .RepeatOnce },
836+ }
837+ s .Require ().NoError (s .DB .Create (unrelated ).Error )
838+
839+ inactive := & models.Task {
840+ ID : 60 ,
841+ Title : "Old groceries list" ,
842+ CreatedBy : s .testUser .ID ,
843+ NextDueDate : & due1 ,
844+ IsActive : true ,
845+ Frequency : models.Frequency {Type : models .RepeatOnce },
846+ }
847+ s .Require ().NoError (s .DB .Create (inactive ).Error )
848+ s .Require ().NoError (s .DB .Model (& models.Task {}).Where ("id = ?" , 60 ).Update ("is_active" , false ).Error )
849+
850+ otherUser := & models.User {}
851+ s .Require ().NoError (s .DB .Create (otherUser ).Error )
852+ otherUserTask := & models.Task {
853+ Title : "Their groceries" ,
854+ CreatedBy : otherUser .ID ,
855+ NextDueDate : & due1 ,
856+ IsActive : true ,
857+ Frequency : models.Frequency {Type : models .RepeatOnce },
858+ }
859+ s .Require ().NoError (s .DB .Create (otherUserTask ).Error )
860+
861+ // Case-insensitive, matches substring, only active tasks for this user
862+ result , err := s .repo .SearchTasksByTitle (ctx , s .testUser .ID , "grocer" )
863+ s .Require ().NoError (err )
864+ s .Require ().Len (result , 2 )
865+ s .Equal ("Sort Groceries in pantry" , result [0 ].Title )
866+ s .Equal ("Buy groceries" , result [1 ].Title )
867+
868+ // LIKE wildcards in query are treated literally
869+ resultLiteral , err := s .repo .SearchTasksByTitle (ctx , s .testUser .ID , "%" )
870+ s .Require ().NoError (err )
871+ s .Require ().Len (resultLiteral , 0 )
872+ }
0 commit comments