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