@@ -781,3 +781,222 @@ func TestAsyncTask_Cleanup(t *testing.T) {
781781 t .Errorf ("Expected 'task not found' error, got '%s'" , errorResponse ["error" ])
782782 }
783783}
784+
785+ // Test: MergeStderr configuration
786+ func TestTask_MergeStderr (t * testing.T ) {
787+ store , apiKey := createTestKeyStore (t )
788+
789+ tasks := map [string ]Task {
790+ "merge-stderr-false" : {
791+ Command : []string {getTestScriptPath (t , "stderr_test.sh" )},
792+ APIKeyNames : []string {"test-key" },
793+ Async : false ,
794+ MergeStderr : false , // Keep separate
795+ ExecutionTimeoutSeconds : 5 ,
796+ },
797+ "merge-stderr-true" : {
798+ Command : []string {getTestScriptPath (t , "stderr_test.sh" )},
799+ APIKeyNames : []string {"test-key" },
800+ Async : false ,
801+ MergeStderr : true , // Merge into stdout
802+ ExecutionTimeoutSeconds : 5 ,
803+ },
804+ }
805+
806+ router := createTestRouter (t , tasks , store )
807+
808+ // Test with MergeStderr = false (separate streams)
809+ req := httptest .NewRequest ("POST" , "/tasks/merge-stderr-false" , nil )
810+ req .Header .Set ("Authorization" , "Bearer " + apiKey )
811+ w := httptest .NewRecorder ()
812+ router .ServeHTTP (w , req )
813+
814+ var result1 taskExecutionResult
815+ json .NewDecoder (w .Body ).Decode (& result1 )
816+
817+ if ! strings .Contains (result1 .StdOut , "This is stdout" ) {
818+ t .Errorf ("Expected stdout to contain 'This is stdout', got '%s'" , result1 .StdOut )
819+ }
820+ if ! strings .Contains (result1 .StdErr , "This is stderr" ) {
821+ t .Errorf ("Expected stderr to contain 'This is stderr', got '%s'" , result1 .StdErr )
822+ }
823+ if strings .Contains (result1 .StdOut , "This is stderr" ) {
824+ t .Errorf ("stderr should not be in stdout when MergeStderr=false" )
825+ }
826+
827+ // Test with MergeStderr = true (merged into stdout)
828+ req = httptest .NewRequest ("POST" , "/tasks/merge-stderr-true" , nil )
829+ req .Header .Set ("Authorization" , "Bearer " + apiKey )
830+ w = httptest .NewRecorder ()
831+ router .ServeHTTP (w , req )
832+
833+ var result2 taskExecutionResult
834+ json .NewDecoder (w .Body ).Decode (& result2 )
835+
836+ if ! strings .Contains (result2 .StdOut , "This is stdout" ) {
837+ t .Errorf ("Expected stdout to contain 'This is stdout', got '%s'" , result2 .StdOut )
838+ }
839+ if ! strings .Contains (result2 .StdOut , "This is stderr" ) {
840+ t .Errorf ("Expected stderr to be merged into stdout, got '%s'" , result2 .StdOut )
841+ }
842+ if result2 .StdErr != "" {
843+ t .Errorf ("Expected stderr to be empty when MergeStderr=true, got '%s'" , result2 .StdErr )
844+ }
845+ }
846+
847+ // Test: MaxInputBytes limit
848+ func TestTask_MaxInputBytes (t * testing.T ) {
849+ store , apiKey := createTestKeyStore (t )
850+
851+ tasks := map [string ]Task {
852+ "limited-input" : {
853+ Command : []string {getTestScriptPath (t , "echo_stdin.sh" )},
854+ APIKeyNames : []string {"test-key" },
855+ Async : false ,
856+ MaxInputBytes : 10 , // Only 10 bytes allowed
857+ ExecutionTimeoutSeconds : 5 ,
858+ },
859+ }
860+
861+ router := createTestRouter (t , tasks , store )
862+
863+ // Test with input within limit
864+ smallInput := "small"
865+ req := httptest .NewRequest ("POST" , "/tasks/limited-input" , strings .NewReader (smallInput ))
866+ req .Header .Set ("Authorization" , "Bearer " + apiKey )
867+ w := httptest .NewRecorder ()
868+ router .ServeHTTP (w , req )
869+
870+ if w .Code != http .StatusOK {
871+ t .Errorf ("Expected status 200 for small input, got %d" , w .Code )
872+ }
873+
874+ // Test with input exceeding limit
875+ largeInput := strings .Repeat ("x" , 100 )
876+ req = httptest .NewRequest ("POST" , "/tasks/limited-input" , strings .NewReader (largeInput ))
877+ req .Header .Set ("Authorization" , "Bearer " + apiKey )
878+ w = httptest .NewRecorder ()
879+ router .ServeHTTP (w , req )
880+
881+ if w .Code != http .StatusBadRequest {
882+ t .Errorf ("Expected status 400 for large input, got %d" , w .Code )
883+ }
884+ }
885+
886+ // Test: MaxOutputBytes limit
887+ func TestTask_MaxOutputBytes (t * testing.T ) {
888+ store , apiKey := createTestKeyStore (t )
889+
890+ tasks := map [string ]Task {
891+ "limited-output" : {
892+ Command : []string {getTestScriptPath (t , "large_output.sh" )},
893+ APIKeyNames : []string {"test-key" },
894+ Async : false ,
895+ MaxOutputBytes : 50 , // Only 50 bytes
896+ ExecutionTimeoutSeconds : 5 ,
897+ },
898+ }
899+
900+ router := createTestRouter (t , tasks , store )
901+
902+ req := httptest .NewRequest ("POST" , "/tasks/limited-output" , nil )
903+ req .Header .Set ("Authorization" , "Bearer " + apiKey )
904+ w := httptest .NewRecorder ()
905+ router .ServeHTTP (w , req )
906+
907+ if w .Code != http .StatusOK {
908+ t .Errorf ("Expected status 200, got %d" , w .Code )
909+ }
910+
911+ var result taskExecutionResult
912+ json .NewDecoder (w .Body ).Decode (& result )
913+
914+ // Output should be truncated to MaxOutputBytes
915+ if len (result .StdOut ) > 50 {
916+ t .Errorf ("Expected stdout to be truncated to 50 bytes, got %d bytes" , len (result .StdOut ))
917+ }
918+ }
919+
920+ // Test: WebhookSecrets
921+ func TestTask_WebhookSecrets (t * testing.T ) {
922+ store , _ := createTestKeyStore (t )
923+
924+ webhookHash := "test-webhook-hash-123"
925+
926+ tasks := map [string ]Task {
927+ "webhook-task" : {
928+ Command : []string {getTestScriptPath (t , "fast_task.sh" )},
929+ Async : false ,
930+ WebhookSecrets : map [string ]string {"webhook1" : webhookHash },
931+ ExecutionTimeoutSeconds : 5 ,
932+ },
933+ }
934+
935+ router := createTestRouter (t , tasks , store )
936+
937+ // Test webhook without API key (should work)
938+ req := httptest .NewRequest ("POST" , "/wh/" + webhookHash , nil )
939+ w := httptest .NewRecorder ()
940+ router .ServeHTTP (w , req )
941+
942+ if w .Code != http .StatusOK {
943+ t .Errorf ("Expected status 200 for webhook, got %d" , w .Code )
944+ }
945+
946+ var result taskExecutionResult
947+ json .NewDecoder (w .Body ).Decode (& result )
948+
949+ if result .Status != "success" {
950+ t .Errorf ("Expected status 'success', got '%s'" , result .Status )
951+ }
952+
953+ // Test with wrong webhook hash (should 404)
954+ req = httptest .NewRequest ("POST" , "/wh/wrong-hash" , nil )
955+ w = httptest .NewRecorder ()
956+ router .ServeHTTP (w , req )
957+
958+ if w .Code != http .StatusNotFound {
959+ t .Errorf ("Expected status 404 for wrong webhook hash, got %d" , w .Code )
960+ }
961+ }
962+
963+ // Test: WebhookSecretFiles
964+ func TestTask_WebhookSecretFiles (t * testing.T ) {
965+ store , _ := createTestKeyStore (t )
966+
967+ // Create a temporary file with webhook hash
968+ tempDir := t .TempDir ()
969+ webhookFile := tempDir + "/webhook-secret"
970+ webhookHash := "file-webhook-hash-456"
971+ err := os .WriteFile (webhookFile , []byte (webhookHash + "\n " ), 0600 )
972+ if err != nil {
973+ t .Fatalf ("Failed to create webhook file: %v" , err )
974+ }
975+
976+ tasks := map [string ]Task {
977+ "webhook-file-task" : {
978+ Command : []string {getTestScriptPath (t , "fast_task.sh" )},
979+ Async : false ,
980+ WebhookSecretFiles : map [string ]string {"webhook2" : webhookFile },
981+ ExecutionTimeoutSeconds : 5 ,
982+ },
983+ }
984+
985+ router := createTestRouter (t , tasks , store )
986+
987+ // Test webhook from file (should work)
988+ req := httptest .NewRequest ("POST" , "/wh/" + webhookHash , nil )
989+ w := httptest .NewRecorder ()
990+ router .ServeHTTP (w , req )
991+
992+ if w .Code != http .StatusOK {
993+ t .Errorf ("Expected status 200 for webhook from file, got %d" , w .Code )
994+ }
995+
996+ var result taskExecutionResult
997+ json .NewDecoder (w .Body ).Decode (& result )
998+
999+ if result .Status != "success" {
1000+ t .Errorf ("Expected status 'success', got '%s'" , result .Status )
1001+ }
1002+ }
0 commit comments