@@ -714,3 +714,176 @@ func TestPipelineCancel_NoToken(t *testing.T) {
714714
715715 assert .Equal (t , result .ExitCode , 3 , "stderr: %s" , result .Stderr )
716716}
717+
718+ // --- pipeline search tests ---
719+
720+ const searchProjectID = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
721+
722+ func fakeProjectInfo (slug , id string ) map [string ]any {
723+ return map [string ]any {
724+ "id" : id ,
725+ "slug" : slug ,
726+ "name" : "testrepo" ,
727+ }
728+ }
729+
730+ func fakeSearchPipeline (id string , number int , status , branch string ) map [string ]any {
731+ now := time .Date (2020 , 1 , 1 , 12 , 0 , 0 , 0 , time .UTC )
732+ return map [string ]any {
733+ "id" : id ,
734+ "number" : number ,
735+ "state" : "created" ,
736+ "status" : status ,
737+ "created_at" : now .Format (time .RFC3339 ),
738+ "updated_at" : now .Format (time .RFC3339 ),
739+ "trigger" : map [string ]any {
740+ "type" : "webhook" ,
741+ "received_at" : now .Format (time .RFC3339 ),
742+ "actor" : map [string ]any {"id" : "actor-uuid-1" },
743+ },
744+ "vcs" : map [string ]any {
745+ "branch" : branch ,
746+ "revision" : "abc1234def5678" ,
747+ },
748+ "project" : map [string ]any {
749+ "id" : searchProjectID ,
750+ },
751+ "workflows_summary" : map [string ]any {
752+ "count_by_status" : map [string ]any {
753+ "success" : 2 ,
754+ "failed" : 1 ,
755+ },
756+ },
757+ }
758+ }
759+
760+ func TestPipelineSearch (t * testing.T ) {
761+ fake := fakes .NewCircleCI (t )
762+ fake .AddProjectInfo (watchSlug , fakeProjectInfo (watchSlug , searchProjectID ))
763+ fake .SetSearchResponse (map [string ]any {
764+ "items" : []any {
765+ fakeSearchPipeline ("pid-search-1" , 10 , "success" , "main" ),
766+ fakeSearchPipeline ("pid-search-2" , 9 , "failed" , "feature" ),
767+ },
768+ "next_page_token" : nil ,
769+ "total_size" : 2 ,
770+ })
771+
772+ env := testenv .New (t )
773+ env .Token = testToken
774+ env .CircleCIURL = fake .URL ()
775+
776+ result := binary .RunCLI (t , binary.RunOpts {
777+ Binary : binaryPath ,
778+ Args : []string {"pipeline" , "search" , "--project" , watchSlug },
779+ Env : env .Environ (),
780+ WorkDir : t .TempDir (),
781+ })
782+
783+ assert .Equal (t , result .ExitCode , 0 , "stderr: %s" , result .Stderr )
784+ assert .Check (t , golden .String (result .Stdout , t .Name ()+ ".txt" ))
785+ }
786+
787+ func TestPipelineSearch_JSON (t * testing.T ) {
788+ fake := fakes .NewCircleCI (t )
789+ fake .AddProjectInfo (watchSlug , fakeProjectInfo (watchSlug , searchProjectID ))
790+ fake .SetSearchResponse (map [string ]any {
791+ "items" : []any {
792+ fakeSearchPipeline ("pid-search-1" , 10 , "success" , "main" ),
793+ },
794+ "next_page_token" : nil ,
795+ "total_size" : 1 ,
796+ })
797+
798+ env := testenv .New (t )
799+ env .Token = testToken
800+ env .CircleCIURL = fake .URL ()
801+
802+ result := binary .RunCLI (t , binary.RunOpts {
803+ Binary : binaryPath ,
804+ Args : []string {"pipeline" , "search" , "--project" , watchSlug , "--json" },
805+ Env : env .Environ (),
806+ WorkDir : t .TempDir (),
807+ })
808+
809+ assert .Equal (t , result .ExitCode , 0 , "stderr: %s" , result .Stderr )
810+ assert .Check (t , golden .String (result .Stdout , t .Name ()+ ".json" ))
811+ }
812+
813+ func TestPipelineSearch_WithFilter (t * testing.T ) {
814+ fake := fakes .NewCircleCI (t )
815+ fake .AddProjectInfo (watchSlug , fakeProjectInfo (watchSlug , searchProjectID ))
816+ fake .SetSearchResponse (map [string ]any {
817+ "items" : []any {fakeSearchPipeline ("pid-search-1" , 10 , "failed" , "main" )},
818+ "next_page_token" : nil ,
819+ "total_size" : 1 ,
820+ })
821+
822+ env := testenv .New (t )
823+ env .Token = testToken
824+ env .CircleCIURL = fake .URL ()
825+
826+ rawFilter := `pipeline.git.branch == "main" and pipeline.state == "errored"`
827+ result := binary .RunCLI (t , binary.RunOpts {
828+ Binary : binaryPath ,
829+ Args : []string {"pipeline" , "search" , "--project" , watchSlug , "--filter" , rawFilter },
830+ Env : env .Environ (),
831+ WorkDir : t .TempDir (),
832+ })
833+
834+ assert .Equal (t , result .ExitCode , 0 , "stderr: %s" , result .Stderr )
835+ assert .Check (t , cmp .Contains (result .Stdout , "pid-search-1" ))
836+
837+ req := fake .LastSearchRequest ()
838+ assert .Assert (t , req != nil , "search endpoint was not called" )
839+ assert .Equal (t , req ["filter" ], rawFilter )
840+ }
841+
842+ // The /pipeline/search API returns "" for timestamps on some pipelines.
843+ // This must not crash the JSON decoder.
844+ func TestPipelineSearch_EmptyTimestamp (t * testing.T ) {
845+ fake := fakes .NewCircleCI (t )
846+ fake .AddProjectInfo (watchSlug , fakeProjectInfo (watchSlug , searchProjectID ))
847+ pip := fakeSearchPipeline ("pid-search-1" , 10 , "success" , "main" )
848+ pip ["created_at" ] = ""
849+ pip ["updated_at" ] = ""
850+ fake .SetSearchResponse (map [string ]any {
851+ "items" : []any {pip },
852+ "next_page_token" : nil ,
853+ "total_size" : 1 ,
854+ })
855+
856+ env := testenv .New (t )
857+ env .Token = testToken
858+ env .CircleCIURL = fake .URL ()
859+
860+ result := binary .RunCLI (t , binary.RunOpts {
861+ Binary : binaryPath ,
862+ Args : []string {"pipeline" , "search" , "--project" , watchSlug },
863+ Env : env .Environ (),
864+ WorkDir : t .TempDir (),
865+ })
866+
867+ assert .Equal (t , result .ExitCode , 0 , "stderr: %s" , result .Stderr )
868+ assert .Check (t , cmp .Contains (result .Stdout , "pid-search-1" ))
869+ }
870+
871+ func TestPipelineSearch_EmptyResults (t * testing.T ) {
872+ fake := fakes .NewCircleCI (t )
873+ fake .AddProjectInfo (watchSlug , fakeProjectInfo (watchSlug , searchProjectID ))
874+ // No SetSearchResponse → handler returns empty items list.
875+
876+ env := testenv .New (t )
877+ env .Token = testToken
878+ env .CircleCIURL = fake .URL ()
879+
880+ result := binary .RunCLI (t , binary.RunOpts {
881+ Binary : binaryPath ,
882+ Args : []string {"pipeline" , "search" , "--project" , watchSlug },
883+ Env : env .Environ (),
884+ WorkDir : t .TempDir (),
885+ })
886+
887+ assert .Equal (t , result .ExitCode , 0 , "stderr: %s" , result .Stderr )
888+ assert .Check (t , cmp .Contains (result .Stderr , "No pipelines found." ))
889+ }
0 commit comments