Repository: mirumee/ariadne-codegen (python)

Issue:
All Upper case for snake case convert
If a query is named `GetBusinessID`, it will be converted to `get_business_i_d`. The better way would be `get_business_id`.

Instead of current implementation in `utils/str_to_snake_case`, I tried to use this regex:

```regex
(?<!^)([A-Z][a-z]|(?<=[a-z])[^a-z]|(?<=[A-Z])[0-9_])(?<!_)
```

But it would also convert from `arg1` to `arg_1`. Though it looks better, this brings lots of breaking in tests. I'm not sure this is a right way since users' code will need many modifications.

Is there any better ways to convert it?

Pull request:
Change `str_to_snake_case` to handle capitalized words
resolves #213 

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d51a02e..6e58c6b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,7 +3,10 @@
 ## UNRELEASED
 
 - Fixed generating results for nullable fields with nullable directives.
-- Changed `include_comments` option to accept enum value, changed default to `"stable"`, deprecated boolean support. Added `get_file_comment` plugin hook. 
+- Changed `include_comments` option to accept enum value, changed default to `"stable"`, deprecated boolean support. Added `get_file_comment` plugin hook.
+- Changed `str_to_snake_case` utility to correctly handle capitalized words.
+- Digits in Python names are now preceded by an underscore (breaking change).
+
 
 ## 0.9.0 (2023-09-11)
 
diff --git a/ariadne_codegen/utils.py b/ariadne_codegen/utils.py
index 838035a..176cc5d 100644
--- a/ariadne_codegen/utils.py
+++ b/ariadne_codegen/utils.py
@@ -33,8 +33,14 @@ def ast_to_str(
 
 def str_to_snake_case(name: str) -> str:
     """Converts camelCase or PascalCase string into snake_case."""
-    result = "".join([f"_{c.lower()}" if c.isupper() else c for c in name])
-    return result[1:] if result.startswith("_") else result
+    # lower-case letters that optionally start with a single upper-case letter
+    lowercase_words = r"[A-Z]?[a-z]+"
+    # upper-case letters, excluding last letter if it is followed by a lower-case letter
+    uppercase_words = r"[A-Z]+(?=[A-Z][a-z]|\d|\W|$)"
+    numbers = r"\d+"
+
+    words = re.findall(rf"{lowercase_words}|{uppercase_words}|{numbers}", name)
+    return "_".join(map(str.lower, words))
 
 
 def str_to_pascal_case(name: str) -> str:


Test patch:
diff --git a/tests/client_generators/input_types_generator/test_names.py b/tests/client_generators/input_types_generator/test_names.py
index ef8bb63..0283a89 100644
--- a/tests/client_generators/input_types_generator/test_names.py
+++ b/tests/client_generators/input_types_generator/test_names.py
@@ -31,7 +31,7 @@ from ...utils import compare_ast, get_assignment_target_names, get_class_def
                 keywords=[],
                 body=[
                     ast.AnnAssign(
-                        target=ast.Name(id="input_field1"),
+                        target=ast.Name(id="input_field_1"),
                         annotation=ast.Name(id="str"),
                         value=ast.Call(
                             func=ast.Name(id=FIELD_CLASS),
@@ -46,13 +46,33 @@ from ...utils import compare_ast, get_assignment_target_names, get_class_def
                         simple=1,
                     ),
                     ast.AnnAssign(
-                        target=ast.Name(id="input_field2"),
+                        target=ast.Name(id="input_field_2"),
                         annotation=ast.Name(id="str"),
+                        value=ast.Call(
+                            func=ast.Name(id=FIELD_CLASS),
+                            args=[],
+                            keywords=[
+                                ast.keyword(
+                                    arg=ALIAS_KEYWORD,
+                                    value=ast.Constant(value="input_field2"),
+                                )
+                            ],
+                        ),
                         simple=1,
                     ),
                     ast.AnnAssign(
-                        target=ast.Name(id="inputfield3"),
+                        target=ast.Name(id="inputfield_3"),
                         annotation=ast.Name(id="str"),
+                        value=ast.Call(
+                            func=ast.Name(id=FIELD_CLASS),
+                            args=[],
+                            keywords=[
+                                ast.keyword(
+                                    arg=ALIAS_KEYWORD,
+                                    value=ast.Constant(value="inputfield3"),
+                                )
+                            ],
+                        ),
                         simple=1,
                     ),
                 ],
@@ -72,7 +92,7 @@ from ...utils import compare_ast, get_assignment_target_names, get_class_def
                 keywords=[],
                 body=[
                     ast.AnnAssign(
-                        target=ast.Name(id="input_field1"),
+                        target=ast.Name(id="input_field_1"),
                         annotation=ast.Name(id="str"),
                         value=ast.Call(
                             func=ast.Name(id=FIELD_CLASS),
@@ -90,7 +110,7 @@ from ...utils import compare_ast, get_assignment_target_names, get_class_def
                         simple=1,
                     ),
                     ast.AnnAssign(
-                        target=ast.Name(id="input_field2"),
+                        target=ast.Name(id="input_field_2"),
                         annotation=ast.Subscript(
                             value=ast.Name(id="List"), slice=ast.Name(id="int")
                         ),
@@ -183,7 +203,7 @@ def test_generate_returns_module_with_valid_field_names(
         "and_",
         "foo",
         "bar",
-        "baz_",
+        "baz",
         "underscore_named_field_",
         "schema_",
     }
diff --git a/tests/client_generators/input_types_generator/test_parsing_inputs.py b/tests/client_generators/input_types_generator/test_parsing_inputs.py
index d036529..3a8e2b6 100644
--- a/tests/client_generators/input_types_generator/test_parsing_inputs.py
+++ b/tests/client_generators/input_types_generator/test_parsing_inputs.py
@@ -3,7 +3,11 @@ import ast
 import pytest
 from graphql import build_ast_schema, parse
 
-from ariadne_codegen.client_generators.constants import BASE_MODEL_CLASS_NAME
+from ariadne_codegen.client_generators.constants import (
+    ALIAS_KEYWORD,
+    BASE_MODEL_CLASS_NAME,
+    FIELD_CLASS,
+)
 from ariadne_codegen.client_generators.input_types import InputTypesGenerator
 
 from ...utils import compare_ast, filter_class_defs
@@ -31,13 +35,33 @@ from ...utils import compare_ast, filter_class_defs
                     decorator_list=[],
                     body=[
                         ast.AnnAssign(
-                            target=ast.Name(id="field1"),
+                            target=ast.Name(id="field_1"),
                             annotation=ast.Name(id='"CustomInput2"'),
+                            value=ast.Call(
+                                func=ast.Name(id=FIELD_CLASS),
+                                args=[],
+                                keywords=[
+                                    ast.keyword(
+                                        arg=ALIAS_KEYWORD,
+                                        value=ast.Constant(value="field1"),
+                                    )
+                                ],
+                            ),
                             simple=1,
                         ),
                         ast.AnnAssign(
-                            target=ast.Name(id="field2"),
+                            target=ast.Name(id="field_2"),
                             annotation=ast.Name(id="int"),
+                            value=ast.Call(
+                                func=ast.Name(id=FIELD_CLASS),
+                                args=[],
+                                keywords=[
+                                    ast.keyword(
+                                        arg=ALIAS_KEYWORD,
+                                        value=ast.Constant(value="field2"),
+                                    )
+                                ],
+                            ),
                             simple=1,
                         ),
                     ],
diff --git a/tests/client_generators/result_types_generator/test_directives.py b/tests/client_generators/result_types_generator/test_directives.py
index 106432d..96e9a7e 100644
--- a/tests/client_generators/result_types_generator/test_directives.py
+++ b/tests/client_generators/result_types_generator/test_directives.py
@@ -206,6 +206,7 @@ def test_generator_returns_module_with_handled_skip_and_include_directives(direc
             OperationDefinitionNode, parse(query_str).definitions[0]
         ),
         enums_module_name="enums",
+        convert_to_snake_case=False,
     )
 
     module = generator.generate()
diff --git a/tests/client_generators/result_types_generator/test_names.py b/tests/client_generators/result_types_generator/test_names.py
index 4797615..ec0c400 100644
--- a/tests/client_generators/result_types_generator/test_names.py
+++ b/tests/client_generators/result_types_generator/test_names.py
@@ -140,8 +140,8 @@ def test_generate_returns_module_with_valid_field_names():
     field_names = get_assignment_target_names(class_def)
     assert field_names == {
         "in_",
-        "field4",
-        "field5",
+        "field_4",
+        "field_5",
         "underscore_named_field_",
         "schema_",
     }
diff --git a/tests/client_generators/result_types_generator/test_parsing_fragments.py b/tests/client_generators/result_types_generator/test_parsing_fragments.py
index 6b46555..1e7027d 100644
--- a/tests/client_generators/result_types_generator/test_parsing_fragments.py
+++ b/tests/client_generators/result_types_generator/test_parsing_fragments.py
@@ -55,6 +55,7 @@ def test_get_classes_returns_list_with_types_generated_from_fragment():
         schema=build_schema(SCHEMA_STR),
         operation_definition=fragment_definition,
         enums_module_name="enums",
+        convert_to_snake_case=False,
     )
 
     generated_class_defs = generator.get_classes()
@@ -112,6 +113,7 @@ def test_get_classes_returns_types_generated_from_fragment_which_uses_other_frag
             "TestFragment": fragment_definition,
             "TestNestedFragment": nested_fragment_definition,
         },
+        convert_to_snake_case=False,
     )
 
     generated_class_defs = generator.get_classes()
diff --git a/tests/client_generators/result_types_generator/test_parsing_operations.py b/tests/client_generators/result_types_generator/test_parsing_operations.py
index 80fe9e3..9656f41 100644
--- a/tests/client_generators/result_types_generator/test_parsing_operations.py
+++ b/tests/client_generators/result_types_generator/test_parsing_operations.py
@@ -8,6 +8,7 @@ from ariadne_codegen.client_generators.constants import (
     ALIAS_KEYWORD,
     BASE_MODEL_CLASS_NAME,
     DISCRIMINATOR_KEYWORD,
+    FIELD_CLASS,
     LIST,
     LITERAL,
     OPTIONAL,
@@ -40,7 +41,7 @@ from .schema import SCHEMA_STR
                     keywords=[],
                     body=[
                         ast.AnnAssign(
-                            target=ast.Name(id="query2"),
+                            target=ast.Name(id="query_2"),
                             annotation=ast.Subscript(
                                 value=ast.Name(id=OPTIONAL),
                                 slice=ast.Subscript(
@@ -48,6 +49,16 @@ from .schema import SCHEMA_STR
                                     slice=ast.Name(id='"CustomQueryQuery2"'),
                                 ),
                             ),
+                            value=ast.Call(
+                                func=ast.Name(id=FIELD_CLASS),
+                                args=[],
+                                keywords=[
+                                    ast.keyword(
+                                        arg=ALIAS_KEYWORD,
+                                        value=ast.Constant(value="query2"),
+                                    )
+                                ],
+                            ),
                             simple=1,
                         )
                     ],
@@ -88,11 +99,21 @@ from .schema import SCHEMA_STR
                     keywords=[],
                     body=[
                         ast.AnnAssign(
-                            target=ast.Name(id="query1"),
+                            target=ast.Name(id="query_1"),
                             annotation=ast.Subscript(
                                 value=ast.Name(id=OPTIONAL),
                                 slice=ast.Name(id='"CustomQueryQuery1"'),
                             ),
+                            value=ast.Call(
+                                func=ast.Name(id=FIELD_CLASS),
+                                args=[],
+                                keywords=[
+                                    ast.keyword(
+                                        arg=ALIAS_KEYWORD,
+                                        value=ast.Constant(value="query1"),
+                                    )
+                                ],
+                            ),
                             simple=1,
                         )
                     ],
@@ -104,21 +125,51 @@ from .schema import SCHEMA_STR
                     keywords=[],
                     body=[
                         ast.AnnAssign(
-                            target=ast.Name(id="field1"),
+                            target=ast.Name(id="field_1"),
                             annotation=ast.Name(id='"CustomQueryQuery1Field1"'),
+                            value=ast.Call(
+                                func=ast.Name(id=FIELD_CLASS),
+                                args=[],
+                                keywords=[
+                                    ast.keyword(
+                                        arg=ALIAS_KEYWORD,
+                                        value=ast.Constant(value="field1"),
+                                    )
+                                ],
+                            ),
                             simple=1,
                         ),
                         ast.AnnAssign(
-                            target=ast.Name(id="field2"),
+                            target=ast.Name(id="field_2"),
                             annotation=ast.Subscript(
                                 value=ast.Name(id=OPTIONAL),
                                 slice=ast.Name(id='"CustomQueryQuery1Field2"'),
                             ),
+                            value=ast.Call(
+                                func=ast.Name(id=FIELD_CLASS),
+                                args=[],
+                                keywords=[
+                                    ast.keyword(
+                                        arg=ALIAS_KEYWORD,
+                                        value=ast.Constant(value="field2"),
+                                    )
+                                ],
+                            ),
                             simple=1,
                         ),
                         ast.AnnAssign(
-                            target=ast.Name(id="field3"),
+                            target=ast.Name(id="field_3"),
                             annotation=ast.Name(id="CustomEnum"),
+                            value=ast.Call(
+                                func=ast.Name(id=FIELD_CLASS),
+                                args=[],
+                                keywords=[
+                                    ast.keyword(
+                                        arg=ALIAS_KEYWORD,
+                                        value=ast.Constant(value="field3"),
+                                    )
+                                ],
+                            ),
                             simple=1,
                         ),
                     ],
@@ -197,8 +248,18 @@ def test_generate_returns_module_with_types_generated_from_mutation():
             keywords=[],
             body=[
                 ast.AnnAssign(
-                    target=ast.Name(id="mutation1"),
+                    target=ast.Name(id="mutation_1"),
                     annotation=ast.Name(id='"CustomMutationMutation1"'),
+                    value=ast.Call(
+                        func=ast.Name(id=FIELD_CLASS),
+                        args=[],
+                        keywords=[
+                            ast.keyword(
+                                arg=ALIAS_KEYWORD,
+                                value=ast.Constant(value="mutation1"),
+                            )
+                        ],
+                    ),
                     simple=1,
                 )
             ],
@@ -252,8 +313,18 @@ def test_generate_returns_module_with_types_generated_from_subscription():
             keywords=[],
             body=[
                 ast.AnnAssign(
-                    target=ast.Name(id="subscription1"),
+                    target=ast.Name(id="subscription_1"),
                     annotation=ast.Name(id='"CustomSubscriptionSubscription1"'),
+                    value=ast.Call(
+                        func=ast.Name(id=FIELD_CLASS),
+                        args=[],
+                        keywords=[
+                            ast.keyword(
+                                arg=ALIAS_KEYWORD,
+                                value=ast.Constant(value="subscription1"),
+                            )
+                        ],
+                    ),
                     simple=1,
                 )
             ],
@@ -351,11 +422,21 @@ def test_generate_returns_module_with_types_generated_from_query_that_uses_fragm
         keywords=[],
         body=[
             ast.AnnAssign(
-                target=ast.Name(id="field2"),
+                target=ast.Name(id="field_2"),
                 annotation=ast.Subscript(
                     value=ast.Name(id="Optional"),
                     slice=ast.Name(id='"CustomQueryQuery2Field2"'),
                 ),
+                value=ast.Call(
+                    func=ast.Name(id=FIELD_CLASS),
+                    args=[],
+                    keywords=[
+                        ast.keyword(
+                            arg=ALIAS_KEYWORD,
+                            value=ast.Constant(value="field2"),
+                        )
+                    ],
+                ),
                 simple=1,
             ),
         ],
@@ -598,18 +679,8 @@ def test_generate_returns_module_with_class_for_every_appearance_of_type():
                     simple=1,
                 ),
                 ast.AnnAssign(
-                    target=ast.Name(id="camel_case_query"),
+                    target=ast.Name(id="camelCaseQuery"),
                     annotation=ast.Name(id='"CustomQueryCamelCaseQuery"'),
-                    value=ast.Call(
-                        func=ast.Name(id="Field"),
-                        args=[],
-                        keywords=[
-                            ast.keyword(
-                                arg=ALIAS_KEYWORD,
-                                value=ast.Constant(value="camelCaseQuery"),
-                            )
-                        ],
-                    ),
                     simple=1,
                 ),
             ],
@@ -646,6 +717,7 @@ def test_generate_returns_module_with_class_for_every_appearance_of_type():
             OperationDefinitionNode, parse(query_str).definitions[0]
         ),
         enums_module_name="enums",
+        convert_to_snake_case=False,
     )
 
     module = generator.generate()
diff --git a/tests/client_generators/result_types_generator/test_unions.py b/tests/client_generators/result_types_generator/test_unions.py
index 33d1d14..dcbc479 100644
--- a/tests/client_generators/result_types_generator/test_unions.py
+++ b/tests/client_generators/result_types_generator/test_unions.py
@@ -185,6 +185,7 @@ def test_generate_returns_module_with_classes_for_union_fields():
             OperationDefinitionNode, parse(query_str).definitions[0]
         ),
         enums_module_name="enums",
+        convert_to_snake_case=False,
     )
 
     module = generator.generate()
diff --git a/tests/client_generators/test_arguments_generator.py b/tests/client_generators/test_arguments_generator.py
index 526522d..1687a43 100644
--- a/tests/client_generators/test_arguments_generator.py
+++ b/tests/client_generators/test_arguments_generator.py
@@ -234,7 +234,7 @@ def test_generate_returns_arguments_and_dictionary_with_valid_names():
             ast.arg(arg="and_", annotation=ast.Name(id="str")),
             ast.arg(arg="in_", annotation=ast.Name(id="str")),
             ast.arg(arg="field_a", annotation=ast.Name(id="str")),
-            ast.arg(arg="_field_b", annotation=ast.Name(id="str")),
+            ast.arg(arg="field_b", annotation=ast.Name(id="str")),
         ],
         kwonlyargs=[],
         kw_defaults=[],
@@ -253,7 +253,7 @@ def test_generate_returns_arguments_and_dictionary_with_valid_names():
             ast.Name(id="and_"),
             ast.Name(id="in_"),
             ast.Name(id="field_a"),
-            ast.Name(id="_field_b"),
+            ast.Name(id="field_b"),
         ],
     )
 
@@ -417,5 +417,5 @@ def test_generate_triggers_process_name_hook_for_every_arg(mocked_plugin_manager
     assert mocked_plugin_manager.process_name.call_count == 2
     name1 = mocked_plugin_manager.process_name.mock_calls[0].args[0]
     name2 = mocked_plugin_manager.process_name.mock_calls[1].args[0]
-    assert name1 == "arg1"
-    assert name2 == "arg2"
+    assert name1 == "arg_1"
+    assert name2 == "arg_2"
diff --git a/tests/client_generators/test_client_generator.py b/tests/client_generators/test_client_generator.py
index 5575f42..222ff56 100644
--- a/tests/client_generators/test_client_generator.py
+++ b/tests/client_generators/test_client_generator.py
@@ -315,7 +315,7 @@ def test_add_method_generates_correct_async_method_body(
                 slice=ast.Tuple(elts=[ast.Name(id="str"), ast.Name(id="object")]),
             ),
             value=ast.Dict(
-                keys=[ast.Constant(value="arg1")], values=[ast.Name(id="arg1")]
+                keys=[ast.Constant(value="arg1")], values=[ast.Name(id="arg_1")]
             ),
             simple=1,
         ),
@@ -469,7 +469,7 @@ def test_add_method_generates_correct_method_body(
                 slice=ast.Tuple(elts=[ast.Name(id="str"), ast.Name(id="object")]),
             ),
             value=ast.Dict(
-                keys=[ast.Constant(value="arg1")], values=[ast.Name(id="arg1")]
+                keys=[ast.Constant(value="arg1")], values=[ast.Name(id="arg_1")]
             ),
             simple=1,
         ),
diff --git a/tests/client_generators/test_package_generator.py b/tests/client_generators/test_package_generator.py
index 427539a..4c0f331 100644
--- a/tests/client_generators/test_package_generator.py
+++ b/tests/client_generators/test_package_generator.py
@@ -165,13 +165,13 @@ def test_generate_creates_file_with_query_types(tmp_path):
     """
     expected_query_types = """
     class CustomQuery(BaseModel):
-        query1: Optional["CustomQueryQuery1"]
+        query_1: Optional["CustomQueryQuery1"] = Field(alias="query1")
 
 
     class CustomQueryQuery1(BaseModel):
-        field1: Optional[List[Optional[str]]]
-        field2: Optional["CustomQueryQuery1Field2"]
-        field3: CustomEnum
+        field_1: Optional[List[Optional[str]]] = Field(alias="field1")
+        field_2: Optional["CustomQueryQuery1Field2"] = Field(alias="field2")
+        field_3: CustomEnum = Field(alias="field3")
 
 
     class CustomQueryQuery1Field2(BaseModel):
@@ -215,10 +215,10 @@ def test_generate_creates_multiple_query_types_files(tmp_path):
     generator.generate()
 
     package_path = tmp_path / package_name
-    query1_file_path = package_path / "custom_query1.py"
+    query1_file_path = package_path / "custom_query_1.py"
     assert query1_file_path.exists()
     assert query1_file_path.is_file()
-    query2_file_path = package_path / "custom_query2.py"
+    query2_file_path = package_path / "custom_query_2.py"
     assert query2_file_path.exists()
     assert query2_file_path.is_file()
 
@@ -488,11 +488,11 @@ def test_generate_creates_result_types_from_operation_that_uses_fragment(tmp_pat
     """
     expected_types = """
     class CustomQuery(BaseModel):
-        query1: Optional["CustomQueryQuery1"]
+        query_1: Optional["CustomQueryQuery1"] = Field(alias="query1")
 
 
     class CustomQueryQuery1(TestFragment):
-        field3: CustomEnum
+        field_3: CustomEnum = Field(alias="field3")
     """
     query_def, fragment_def = parse(query_str).definitions
     generator = PackageGenerator(
diff --git a/tests/main/clients/multiple_fragments/expected_client/__init__.py b/tests/main/clients/multiple_fragments/expected_client/__init__.py
index 2de69da..7f9e62b 100644
--- a/tests/main/clients/multiple_fragments/expected_client/__init__.py
+++ b/tests/main/clients/multiple_fragments/expected_client/__init__.py
@@ -1,9 +1,9 @@
 from .async_base_client import AsyncBaseClient
 from .base_model import BaseModel, Upload
 from .client import Client
-from .example_query1 import ExampleQuery1, ExampleQuery1ExampleQuery
-from .example_query2 import ExampleQuery2, ExampleQuery2ExampleQuery
-from .example_query3 import ExampleQuery3, ExampleQuery3ExampleQuery
+from .example_query_1 import ExampleQuery1, ExampleQuery1ExampleQuery
+from .example_query_2 import ExampleQuery2, ExampleQuery2ExampleQuery
+from .example_query_3 import ExampleQuery3, ExampleQuery3ExampleQuery
 from .exceptions import (
     GraphQLClientError,
     GraphQLClientGraphQLError,
diff --git a/tests/main/clients/multiple_fragments/expected_client/client.py b/tests/main/clients/multiple_fragments/expected_client/client.py
index cf30da3..1b00b31 100644
--- a/tests/main/clients/multiple_fragments/expected_client/client.py
+++ b/tests/main/clients/multiple_fragments/expected_client/client.py
@@ -1,9 +1,9 @@
 from typing import Dict
 
 from .async_base_client import AsyncBaseClient
-from .example_query1 import ExampleQuery1
-from .example_query2 import ExampleQuery2
-from .example_query3 import ExampleQuery3
+from .example_query_1 import ExampleQuery1
+from .example_query_2 import ExampleQuery2
+from .example_query_3 import ExampleQuery3
 
 
 def gql(q: str) -> str:
@@ -11,7 +11,7 @@ def gql(q: str) -> str:
 
 
 class Client(AsyncBaseClient):
-    async def example_query1(self) -> ExampleQuery1:
+    async def example_query_1(self) -> ExampleQuery1:
         query = gql(
             """
             query exampleQuery1 {
@@ -38,7 +38,7 @@ class Client(AsyncBaseClient):
         data = self.get_data(response)
         return ExampleQuery1.model_validate(data)
 
-    async def example_query2(self) -> ExampleQuery2:
+    async def example_query_2(self) -> ExampleQuery2:
         query = gql(
             """
             query exampleQuery2 {
@@ -66,7 +66,7 @@ class Client(AsyncBaseClient):
         data = self.get_data(response)
         return ExampleQuery2.model_validate(data)
 
-    async def example_query3(self) -> ExampleQuery3:
+    async def example_query_3(self) -> ExampleQuery3:
         query = gql(
             """
             query exampleQuery3 {
diff --git a/tests/main/clients/multiple_fragments/expected_client/example_query1.py b/tests/main/clients/multiple_fragments/expected_client/example_query_1.py
similarity index 100%
rename from tests/main/clients/multiple_fragments/expected_client/example_query1.py
rename to tests/main/clients/multiple_fragments/expected_client/example_query_1.py
diff --git a/tests/main/clients/multiple_fragments/expected_client/example_query2.py b/tests/main/clients/multiple_fragments/expected_client/example_query_2.py
similarity index 100%
rename from tests/main/clients/multiple_fragments/expected_client/example_query2.py
rename to tests/main/clients/multiple_fragments/expected_client/example_query_2.py
diff --git a/tests/main/clients/multiple_fragments/expected_client/example_query3.py b/tests/main/clients/multiple_fragments/expected_client/example_query_3.py
similarity index 100%
rename from tests/main/clients/multiple_fragments/expected_client/example_query3.py
rename to tests/main/clients/multiple_fragments/expected_client/example_query_3.py
diff --git a/tests/test_utils.py b/tests/test_utils.py
index 6e5fba5..7190e72 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -9,6 +9,7 @@ from ariadne_codegen.utils import (
     format_multiline_strings,
     get_variable_indent_size,
     process_name,
+    str_to_snake_case,
 )
 
 
@@ -76,6 +77,50 @@ def test_ast_to_str_removes_unused_imports():
     assert not_used_imported_class not in generated_code
 
 
+@pytest.mark.parametrize(
+    "name, expected_result",
+    [
+        ("test", "test"),
+        ("Test", "test"),
+        ("TEST", "test"),
+        ("test_word", "test_word"),
+        ("TestWord", "test_word"),
+        ("testWord", "test_word"),
+        ("TESTWord", "test_word"),
+        ("testWORD", "test_word"),
+        ("testW", "test_w"),
+        ("TestW", "test_w"),
+        ("test_long_word", "test_long_word"),
+        ("TestLongWord", "test_long_word"),
+        ("testLongWord", "test_long_word"),
+        ("test_LongWord", "test_long_word"),
+        ("testLongWORD", "test_long_word"),
+        ("test123", "test_123"),
+        ("123test", "123_test"),
+        ("Test123", "test_123"),
+        ("123Test", "123_test"),
+        ("TEST123", "test_123"),
+        ("123TEST", "123_test"),
+        ("testWord123", "test_word_123"),
+        ("TestWord123", "test_word_123"),
+        ("testWORD123", "test_word_123"),
+        ("TESTWord123", "test_word_123"),
+        ("test123Word", "test_123_word"),
+        ("Test123Word", "test_123_word"),
+        ("test123WORD", "test_123_word"),
+        ("Test123WORD", "test_123_word"),
+        ("TEST123Word", "test_123_word"),
+        ("testWord123", "test_word_123"),
+        ("TestWord123", "test_word_123"),
+        ("testWORD123", "test_word_123"),
+        ("TestWORD123", "test_word_123"),
+        ("TESTWord123", "test_word_123"),
+    ],
+)
+def test_str_to_snake_case_returns_correct_string(name, expected_result):
+    assert str_to_snake_case(name) == expected_result
+
+
 @pytest.mark.parametrize(
     ["source", "expected_indent"],
     [