@@ -834,3 +834,177 @@ func TestConditionStatusValidation(t *testing.T) {
834834 })
835835 }
836836}
837+
838+ func TestArgsToOrderBy (t * testing.T ) {
839+ tests := []struct {
840+ name string
841+ errorContains string
842+ input []string
843+ expected []string
844+ expectError bool
845+ }{
846+ {
847+ name : "single field with asc" ,
848+ input : []string {"name asc" },
849+ expected : []string {"name asc" },
850+ },
851+ {
852+ name : "single field with desc" ,
853+ input : []string {"created_time desc" },
854+ expected : []string {"created_time desc" },
855+ },
856+ {
857+ name : "single field without direction defaults to asc" ,
858+ input : []string {"created_time" },
859+ expected : []string {"created_time asc" },
860+ },
861+ {
862+ name : "multiple fields" ,
863+ input : []string {"name asc" , "created_time desc" },
864+ expected : []string {"name asc" , "created_time desc" },
865+ },
866+ {
867+ name : "field with extra spaces" ,
868+ input : []string {" name asc " },
869+ expected : []string {"name asc" },
870+ },
871+ {
872+ name : "field with tabs and spaces" ,
873+ input : []string {"name \t desc" },
874+ expected : []string {"name desc" },
875+ },
876+ {
877+ name : "all allowed fields" ,
878+ input : []string {"id" , "name" , "created_time" , "updated_time" , "kind" },
879+ expected : []string {"id asc" , "name asc" , "created_time asc" , "updated_time asc" , "kind asc" },
880+ },
881+ {
882+ name : "invalid direction" ,
883+ input : []string {"name ascending" },
884+ expectError : true ,
885+ errorContains : "invalid order format" ,
886+ },
887+ {
888+ name : "SQL injection attempt - semicolon" ,
889+ input : []string {"name; DROP TABLE resources" },
890+ expectError : true ,
891+ errorContains : "invalid order format" ,
892+ },
893+ {
894+ name : "SQL injection attempt - comment" ,
895+ input : []string {"name-- asc" },
896+ expectError : true ,
897+ errorContains : "invalid order format" ,
898+ },
899+ {
900+ name : "uppercase field name" ,
901+ input : []string {"NAME asc" },
902+ expectError : true ,
903+ errorContains : "invalid order format" ,
904+ },
905+ {
906+ name : "uppercase direction" ,
907+ input : []string {"name ASC" },
908+ expectError : true ,
909+ errorContains : "invalid order format" ,
910+ },
911+ {
912+ name : "empty string in array is skipped" ,
913+ input : []string {"" },
914+ expected : nil ,
915+ },
916+ {
917+ name : "empty string in array with field at end" ,
918+ input : []string {"" , "" , "" , "" , "" , "kind asc" , "href desc" },
919+ expected : []string {"kind asc" , "href desc" },
920+ },
921+ {
922+ name : "whitespace only string is skipped" ,
923+ input : []string {" " },
924+ expected : nil ,
925+ },
926+ {
927+ name : "mixed valid and empty strings and tabs" ,
928+ input : []string {"name asc" , "" , "created_time desc" , " " , "\t " },
929+ expected : []string {"name asc" , "created_time desc" },
930+ },
931+ {
932+ name : "mixed valid and invalid field" ,
933+ input : []string {"created_time desc" , "name" , "wrong_field" },
934+ expectError : true ,
935+ errorContains : "not allowed for ordering" ,
936+ },
937+ {
938+ name : "field not in whitelist" ,
939+ input : []string {"custom_field asc" },
940+ expectError : true ,
941+ errorContains : "not allowed for ordering" ,
942+ },
943+ {
944+ name : "deleted_time field" ,
945+ input : []string {"deleted_time desc" },
946+ expected : []string {"deleted_time desc" },
947+ },
948+ {
949+ name : "generation field" ,
950+ input : []string {"generation asc" },
951+ expected : []string {"generation asc" },
952+ },
953+ {
954+ name : "too many parts" ,
955+ input : []string {"name asc extra" },
956+ expectError : true ,
957+ errorContains : "invalid order format" ,
958+ },
959+ {
960+ name : "empty array" ,
961+ input : []string {},
962+ expected : nil ,
963+ },
964+ }
965+
966+ for _ , tt := range tests {
967+ t .Run (tt .name , func (t * testing.T ) {
968+ RegisterTestingT (t )
969+
970+ result , err := ArgsToOrderBy (tt .input )
971+
972+ if tt .expectError {
973+ Expect (err ).ToNot (BeNil (), "expected error but got nil" )
974+ if tt .errorContains != "" {
975+ Expect (err .Reason ).To (ContainSubstring (tt .errorContains ))
976+ }
977+ } else {
978+ Expect (err ).To (BeNil (), "unexpected error: %v" , err )
979+ Expect (result ).To (Equal (tt .expected ))
980+ }
981+ })
982+ }
983+ }
984+
985+ func TestArgsToOrder_SecurityValidation (t * testing.T ) {
986+ RegisterTestingT (t )
987+
988+ // SQL injection attempts that should all fail
989+ injectionAttempts := []struct {
990+ name string
991+ input string
992+ }{
993+ {"union injection" , "name UNION SELECT password FROM users" },
994+ {"comment injection" , "name--" },
995+ {"semicolon terminator" , "name; DROP TABLE resources;" },
996+ {"quote escape" , "name' OR '1'='1" },
997+ {"parentheses" , "name) OR (1=1" },
998+ {"wildcard" , "name*" },
999+ {"backtick" , "name`" },
1000+ }
1001+
1002+ for _ , tt := range injectionAttempts {
1003+ t .Run (tt .name , func (t * testing.T ) {
1004+ RegisterTestingT (t )
1005+
1006+ _ , err := ArgsToOrderBy ([]string {tt .input })
1007+ Expect (err ).ToNot (BeNil (), "injection attempt '%s' should be rejected" , tt .input )
1008+ })
1009+ }
1010+ }
0 commit comments