@@ -444,3 +444,114 @@ func TestProjectWorkflowPermissions(t *testing.T) {
444444 fmt .Sprintf ("/%s/%s/projects/%d/workflows/%d/delete?_csrf=%s" , user .Name , repo .Name , project .ID , workflow .ID , GetUserCSRFToken (t , session2 )))
445445 session2 .MakeRequest (t , req , http .StatusNotFound ) // we use 404 to avoid leaking existence
446446}
447+
448+ func TestProjectWorkflowValidation (t * testing.T ) {
449+ defer tests .PrepareTestEnv (t )()
450+
451+ user := unittest .AssertExistsAndLoadBean (t , & user_model.User {ID : 2 })
452+ repo := unittest .AssertExistsAndLoadBean (t , & repo_model.Repository {ID : 1 })
453+
454+ // Create a project
455+ project := & project_model.Project {
456+ Title : "Test Project for Workflow Validation" ,
457+ RepoID : repo .ID ,
458+ Type : project_model .TypeRepository ,
459+ TemplateType : project_model .TemplateTypeNone ,
460+ }
461+ err := project_model .NewProject (t .Context (), project )
462+ assert .NoError (t , err )
463+
464+ session := loginUser (t , user .Name )
465+
466+ // Test 1: Try to create a workflow without any actions (should fail)
467+ t .Run ("Create workflow without actions should fail" , func (t * testing.T ) {
468+ workflowData := map [string ]any {
469+ "event_id" : string (project_model .WorkflowEventItemOpened ),
470+ "filters" : map [string ]any {
471+ string (project_model .WorkflowFilterTypeIssueType ): "issue" ,
472+ },
473+ "actions" : map [string ]any {
474+ // No actions provided - this should trigger validation error
475+ },
476+ }
477+
478+ body , err := json .Marshal (workflowData )
479+ assert .NoError (t , err )
480+
481+ req := NewRequestWithBody (t , "POST" ,
482+ fmt .Sprintf ("/%s/%s/projects/%d/workflows/item_opened?_csrf=%s" , user .Name , repo .Name , project .ID , GetUserCSRFToken (t , session )),
483+ strings .NewReader (string (body )))
484+ req .Header .Set ("Content-Type" , "application/json" )
485+ resp := session .MakeRequest (t , req , http .StatusBadRequest )
486+
487+ // Parse response
488+ var result map [string ]any
489+ err = json .Unmarshal (resp .Body .Bytes (), & result )
490+ assert .NoError (t , err )
491+ assert .Equal (t , "NoActions" , result ["error" ], "Error should be NoActions" )
492+ assert .NotEmpty (t , result ["message" ], "Error message should be provided" )
493+ })
494+
495+ // Test 2: Try to update a workflow to have no actions (should fail)
496+ t .Run ("Update workflow to remove all actions should fail" , func (t * testing.T ) {
497+ // First create a valid workflow
498+ column := & project_model.Column {
499+ Title : "Test Column" ,
500+ ProjectID : project .ID ,
501+ }
502+ err := project_model .NewColumn (t .Context (), column )
503+ assert .NoError (t , err )
504+
505+ workflow := & project_model.Workflow {
506+ ProjectID : project .ID ,
507+ WorkflowEvent : project_model .WorkflowEventItemOpened ,
508+ WorkflowFilters : []project_model.WorkflowFilter {
509+ {
510+ Type : project_model .WorkflowFilterTypeIssueType ,
511+ Value : "issue" ,
512+ },
513+ },
514+ WorkflowActions : []project_model.WorkflowAction {
515+ {
516+ Type : project_model .WorkflowActionTypeColumn ,
517+ Value : strconv .FormatInt (column .ID , 10 ),
518+ },
519+ },
520+ Enabled : true ,
521+ }
522+ err = project_model .CreateWorkflow (t .Context (), workflow )
523+ assert .NoError (t , err )
524+
525+ // Try to update it to have no actions
526+ updateData := map [string ]any {
527+ "event_id" : strconv .FormatInt (workflow .ID , 10 ),
528+ "filters" : map [string ]any {
529+ string (project_model .WorkflowFilterTypeIssueType ): "issue" ,
530+ },
531+ "actions" : map [string ]any {
532+ // No actions - should fail
533+ },
534+ }
535+
536+ body , err := json .Marshal (updateData )
537+ assert .NoError (t , err )
538+
539+ req := NewRequestWithBody (t , "POST" ,
540+ fmt .Sprintf ("/%s/%s/projects/%d/workflows/%d?_csrf=%s" , user .Name , repo .Name , project .ID , workflow .ID , GetUserCSRFToken (t , session )),
541+ strings .NewReader (string (body )))
542+ req .Header .Set ("Content-Type" , "application/json" )
543+ resp := session .MakeRequest (t , req , http .StatusBadRequest )
544+
545+ // Parse response
546+ var result map [string ]any
547+ err = json .Unmarshal (resp .Body .Bytes (), & result )
548+ assert .NoError (t , err )
549+ assert .Equal (t , "NoActions" , result ["error" ], "Error should be NoActions" )
550+ assert .NotEmpty (t , result ["message" ], "Error message should be provided" )
551+
552+ // Verify the workflow was not changed
553+ unchangedWorkflow , err := project_model .GetWorkflowByID (t .Context (), workflow .ID )
554+ assert .NoError (t , err )
555+ assert .Len (t , unchangedWorkflow .WorkflowActions , 1 , "Workflow should still have the original action" )
556+ })
557+ }
0 commit comments