AIUnitTest 0.0.2__tar.gz → 0.0.5__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. {aiunittest-0.0.2 → aiunittest-0.0.5}/.github/workflows/ci.yml +10 -3
  2. aiunittest-0.0.5/.github/workflows/release.yml +86 -0
  3. {aiunittest-0.0.2 → aiunittest-0.0.5}/.pre-commit-config.yaml +5 -1
  4. {aiunittest-0.0.2/src/AIUnitTest.egg-info → aiunittest-0.0.5}/PKG-INFO +1 -1
  5. {aiunittest-0.0.2 → aiunittest-0.0.5}/pyproject.toml +3 -0
  6. {aiunittest-0.0.2 → aiunittest-0.0.5/src/AIUnitTest.egg-info}/PKG-INFO +1 -1
  7. {aiunittest-0.0.2 → aiunittest-0.0.5}/src/AIUnitTest.egg-info/SOURCES.txt +1 -1
  8. aiunittest-0.0.5/src/AIUnitTest.egg-info/entry_points.txt +2 -0
  9. {aiunittest-0.0.2 → aiunittest-0.0.5}/src/ai_unit_test/_version.py +2 -2
  10. {aiunittest-0.0.2 → aiunittest-0.0.5}/src/ai_unit_test/cli.py +9 -6
  11. {aiunittest-0.0.2 → aiunittest-0.0.5}/src/ai_unit_test/file_helper.py +13 -0
  12. {aiunittest-0.0.2 → aiunittest-0.0.5}/tests/unit/test_cli.py +4 -2
  13. aiunittest-0.0.2/.github/workflows/create-release.yml +0 -53
  14. aiunittest-0.0.2/.github/workflows/release.yml +0 -60
  15. {aiunittest-0.0.2 → aiunittest-0.0.5}/.flake8 +0 -0
  16. {aiunittest-0.0.2 → aiunittest-0.0.5}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  17. {aiunittest-0.0.2 → aiunittest-0.0.5}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  18. {aiunittest-0.0.2 → aiunittest-0.0.5}/.github/ISSUE_TEMPLATE/documentation.yml +0 -0
  19. {aiunittest-0.0.2 → aiunittest-0.0.5}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  20. {aiunittest-0.0.2 → aiunittest-0.0.5}/.github/dependabot.yml +0 -0
  21. {aiunittest-0.0.2 → aiunittest-0.0.5}/.github/pull_request_template.md +0 -0
  22. {aiunittest-0.0.2 → aiunittest-0.0.5}/.gitignore +0 -0
  23. {aiunittest-0.0.2 → aiunittest-0.0.5}/.markdownlint.yml +0 -0
  24. {aiunittest-0.0.2 → aiunittest-0.0.5}/CONTRIBUTING.md +0 -0
  25. {aiunittest-0.0.2 → aiunittest-0.0.5}/LICENSE +0 -0
  26. {aiunittest-0.0.2 → aiunittest-0.0.5}/README.md +0 -0
  27. {aiunittest-0.0.2 → aiunittest-0.0.5}/USAGE.md +0 -0
  28. {aiunittest-0.0.2 → aiunittest-0.0.5}/conda/meta.yaml +0 -0
  29. {aiunittest-0.0.2 → aiunittest-0.0.5}/docs/initial.md +0 -0
  30. {aiunittest-0.0.2 → aiunittest-0.0.5}/requirements.txt +0 -0
  31. {aiunittest-0.0.2 → aiunittest-0.0.5}/reset_fake_project.py +0 -0
  32. {aiunittest-0.0.2 → aiunittest-0.0.5}/setup.cfg +0 -0
  33. {aiunittest-0.0.2 → aiunittest-0.0.5}/src/AIUnitTest.egg-info/dependency_links.txt +0 -0
  34. {aiunittest-0.0.2 → aiunittest-0.0.5}/src/AIUnitTest.egg-info/requires.txt +0 -0
  35. {aiunittest-0.0.2 → aiunittest-0.0.5}/src/AIUnitTest.egg-info/top_level.txt +0 -0
  36. {aiunittest-0.0.2 → aiunittest-0.0.5}/src/ai_unit_test/__init__.py +0 -0
  37. {aiunittest-0.0.2 → aiunittest-0.0.5}/src/ai_unit_test/coverage_helper.py +0 -0
  38. {aiunittest-0.0.2 → aiunittest-0.0.5}/src/ai_unit_test/llm.py +0 -0
  39. {aiunittest-0.0.2 → aiunittest-0.0.5}/src/ai_unit_test/main.py +0 -0
  40. {aiunittest-0.0.2 → aiunittest-0.0.5}/src/ai_unit_test/py.typed +0 -0
  41. {aiunittest-0.0.2 → aiunittest-0.0.5}/tests/fake_project/pyproject.toml +0 -0
  42. {aiunittest-0.0.2 → aiunittest-0.0.5}/tests/fake_project/src/__init__.py +0 -0
  43. {aiunittest-0.0.2 → aiunittest-0.0.5}/tests/fake_project/src/simple_math.py +0 -0
  44. {aiunittest-0.0.2 → aiunittest-0.0.5}/tests/fake_project/tests/test_simple_math.py +0 -0
  45. {aiunittest-0.0.2 → aiunittest-0.0.5}/tests/unit/conftest.py +0 -0
  46. {aiunittest-0.0.2 → aiunittest-0.0.5}/tests/unit/create_fake_coverage.py +0 -0
  47. {aiunittest-0.0.2 → aiunittest-0.0.5}/tests/unit/dummy_source.py +0 -0
  48. {aiunittest-0.0.2 → aiunittest-0.0.5}/tests/unit/fake.coverage +0 -0
  49. {aiunittest-0.0.2 → aiunittest-0.0.5}/tests/unit/fake_pyproject.toml +0 -0
  50. {aiunittest-0.0.2 → aiunittest-0.0.5}/tests/unit/test_coverage_helper.py +0 -0
  51. {aiunittest-0.0.2 → aiunittest-0.0.5}/tests/unit/test_dummy_source.py +0 -0
  52. {aiunittest-0.0.2 → aiunittest-0.0.5}/tests/unit/test_file_helper.py +0 -0
  53. {aiunittest-0.0.2 → aiunittest-0.0.5}/tests/unit/test_llm.py +0 -0
  54. {aiunittest-0.0.2 → aiunittest-0.0.5}/tests/unit/test_main.py +0 -0
@@ -8,9 +8,11 @@ jobs:
8
8
  setup:
9
9
  name: Setup & Lint
10
10
  runs-on: ubuntu-latest
11
+ permissions:
12
+ contents: read
11
13
 
12
14
  steps:
13
- - uses: actions/checkout@v4
15
+ - uses: actions/checkout@v5
14
16
 
15
17
  - name: Setup Python 3.13 (with pip cache)
16
18
  uses: actions/setup-python@v5
@@ -44,7 +46,7 @@ jobs:
44
46
  contents: read
45
47
  pull-requests: write
46
48
  steps:
47
- - uses: actions/checkout@v4
49
+ - uses: actions/checkout@v5
48
50
 
49
51
  - uses: actions/setup-python@v5
50
52
  with:
@@ -81,7 +83,7 @@ jobs:
81
83
  contents: read
82
84
  pull-requests: write
83
85
  steps:
84
- - uses: actions/checkout@v4
86
+ - uses: actions/checkout@v5
85
87
  - uses: actions/setup-python@v5
86
88
  with:
87
89
  python-version: "3.13"
@@ -89,6 +91,9 @@ jobs:
89
91
  - name: Install dependencies
90
92
  run: pip install -r requirements.txt
91
93
 
94
+ - name: Install project
95
+ run: pip install -e .
96
+
92
97
  - name: Run tests
93
98
  run: pytest
94
99
 
@@ -99,3 +104,5 @@ jobs:
99
104
  - name: Label dependabot PR
100
105
  if: steps.fetch-metadata.outputs.dependency-type == 'direct:production'
101
106
  run: gh pr edit ${{ github.event.pull_request.html_url }} --add-label "dependencies"
107
+ env:
108
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -0,0 +1,86 @@
1
+ name: Create and Publish Release
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+
8
+ jobs:
9
+ create_and_publish_release:
10
+ runs-on: ubuntu-latest
11
+ permissions:
12
+ contents: write # To create the release and tag
13
+ id-token: write # For authentication with PyPI
14
+
15
+ steps:
16
+ - name: Checkout code
17
+ uses: actions/checkout@v5
18
+ with:
19
+ fetch-depth: 0 # Required for setuptools_scm
20
+
21
+ - name: Get latest version and calculate next
22
+ id: get_next_version
23
+ run: |
24
+ LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0")
25
+ echo "Latest tag: $LATEST_TAG"
26
+
27
+ # Remove 'v' prefix and split into parts
28
+ VERSION_PARTS=$(echo $LATEST_TAG | sed 's/^v//' | tr '.' '\n')
29
+ MAJOR=$(echo $VERSION_PARTS | awk '{print $1}')
30
+ MINOR=$(echo $VERSION_PARTS | awk '{print $2}')
31
+ PATCH=$(echo $VERSION_PARTS | awk '{print $3}')
32
+
33
+ # Increment patch version
34
+ NEXT_PATCH=$((PATCH + 1))
35
+ NEXT_VERSION="v${MAJOR}.${MINOR}.${NEXT_PATCH}"
36
+ echo "Next version: $NEXT_VERSION"
37
+
38
+ echo "NEXT_VERSION=$NEXT_VERSION" >> $GITHUB_OUTPUT
39
+
40
+ - name: Create new tag
41
+ run: |
42
+ git config user.name "github-actions[bot]"
43
+ git config user.email "github-actions[bot]@users.noreply.github.com"
44
+ git tag -a ${{ steps.get_next_version.outputs.NEXT_VERSION }} -m "Release ${{ steps.get_next_version.outputs.NEXT_VERSION }}"
45
+ git push origin ${{ steps.get_next_version.outputs.NEXT_VERSION }}
46
+
47
+ - name: Set up Python
48
+ uses: actions/setup-python@v5
49
+ with:
50
+ python-version: "3.9"
51
+
52
+ - name: Install build dependencies
53
+ run: |
54
+ python -m pip install --upgrade pip
55
+ pip install setuptools_scm build twine
56
+
57
+ - name: Build package
58
+ run: python -m build
59
+
60
+ - name: Create GitHub Release
61
+ uses: ncipollo/release-action@v1
62
+ with:
63
+ tag: ${{ steps.get_next_version.outputs.NEXT_VERSION }}
64
+ name: Release ${{ steps.get_next_version.outputs.NEXT_VERSION }}
65
+ generateReleaseNotes: true
66
+ artifacts: "dist/*"
67
+
68
+ - name: Publish to PyPI
69
+ uses: pypa/gh-action-pypi-publish@release/v1
70
+
71
+ - name: Set up Conda
72
+ uses: conda-incubator/setup-miniconda@v3
73
+ with:
74
+ python-version: 3.9
75
+ auto-update-conda: true
76
+ auto-activate-base: false
77
+
78
+ - name: Build and upload Conda package
79
+ shell: bash -l {0}
80
+ run: |
81
+ conda install -c conda-forge conda-build anaconda-client -y
82
+ conda install -c conda-forge --file conda/meta.yaml --yes
83
+ export GIT_TAG=${{ steps.get_next_version.outputs.NEXT_VERSION }}
84
+ conda build conda --output-folder conda-dist
85
+ anaconda login --with-token ${{ secrets.CONDA_TOKEN }}
86
+ anaconda upload conda-dist/**/*.tar.bz2 --label main --skip-existing
@@ -72,5 +72,9 @@ repos:
72
72
  rev: v1.17.1
73
73
  hooks:
74
74
  - id: mypy
75
- args: ["--config-file", "pyproject.toml"]
75
+ entry: mypy --config-file pyproject.toml
76
+ language: python
77
+ require_serial: true
76
78
  additional_dependencies: ["typer", "openai", "coverage", "pytest"]
79
+ env:
80
+ PYTHONPATH: src
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: AIUnitTest
3
- Version: 0.0.2
3
+ Version: 0.0.5
4
4
  Summary: CLI to generate and update Python unit tests automatically using coverage and AI
5
5
  Author: Ofido
6
6
  Project-URL: Homepage, https://github.com/ofido/AIUnitTest
@@ -24,6 +24,9 @@ dependencies = ["openai", "coverage", "typer", "tomli"]
24
24
  "Homepage" = "https://github.com/ofido/AIUnitTest"
25
25
  "Bug Tracker" = "https://github.com/ofido/AIUnitTest/issues"
26
26
 
27
+ [project.scripts]
28
+ ai-unit-test = "ai_unit_test.cli:app"
29
+
27
30
  [project.optional-dependencies]
28
31
  dev = [
29
32
  "black",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: AIUnitTest
3
- Version: 0.0.2
3
+ Version: 0.0.5
4
4
  Summary: CLI to generate and update Python unit tests automatically using coverage and AI
5
5
  Author: Ofido
6
6
  Project-URL: Homepage, https://github.com/ofido/AIUnitTest
@@ -16,13 +16,13 @@ reset_fake_project.py
16
16
  .github/ISSUE_TEMPLATE/documentation.yml
17
17
  .github/ISSUE_TEMPLATE/feature_request.yml
18
18
  .github/workflows/ci.yml
19
- .github/workflows/create-release.yml
20
19
  .github/workflows/release.yml
21
20
  conda/meta.yaml
22
21
  docs/initial.md
23
22
  src/AIUnitTest.egg-info/PKG-INFO
24
23
  src/AIUnitTest.egg-info/SOURCES.txt
25
24
  src/AIUnitTest.egg-info/dependency_links.txt
25
+ src/AIUnitTest.egg-info/entry_points.txt
26
26
  src/AIUnitTest.egg-info/requires.txt
27
27
  src/AIUnitTest.egg-info/top_level.txt
28
28
  src/ai_unit_test/__init__.py
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ ai-unit-test = ai_unit_test.cli:app
@@ -17,5 +17,5 @@ __version__: str
17
17
  __version_tuple__: VERSION_TUPLE
18
18
  version_tuple: VERSION_TUPLE
19
19
 
20
- __version__ = version = '0.0.2'
21
- __version_tuple__ = version_tuple = (0, 0, 2)
20
+ __version__ = version = '0.0.5'
21
+ __version_tuple__ = version_tuple = (0, 0, 5)
@@ -14,6 +14,7 @@ from ai_unit_test.file_helper import (
14
14
  find_relevant_tests,
15
15
  find_test_file,
16
16
  get_source_code_chunks,
17
+ insert_new_test,
17
18
  read_file_content,
18
19
  write_file_content,
19
20
  )
@@ -154,15 +155,17 @@ async def _process_missing_info(missing_info: dict[Path, list[int]], tests_folde
154
155
  f"(lines {chunk.start_line}-{chunk.end_line}) with uncovered lines: {chunk_uncovered_lines}"
155
156
  )
156
157
  try:
158
+ existing_content = read_file_content(test_file)
157
159
  updated_test: str = await update_test_with_llm(
158
160
  chunk.source_code, # Pass chunk source code
159
- read_file_content(test_file) or "", # Still pass the whole test file for context
161
+ existing_content or "", # Still pass the whole test file for context
160
162
  str(source_file_path),
161
163
  chunk_uncovered_lines, # Pass chunk-specific uncovered lines
162
164
  other_tests_content,
163
165
  test_style, # Pass the detected test style
164
166
  )
165
- write_file_content(test_file, updated_test, mode="a") # Append the new test content
167
+ new_content = insert_new_test(existing_content, updated_test)
168
+ write_file_content(test_file, new_content) # Overwrite the file with the new content
166
169
  logger.info(f"✅ Test file updated successfully: {test_file}")
167
170
  except Exception as exc: # pragma: no cover
168
171
  logger.error(f"Error updating {test_file}: {exc}")
@@ -244,8 +247,6 @@ def func(
244
247
  logger.warning(f"Test file not found for {file_path}, skipping.")
245
248
  return
246
249
 
247
- test_code: str = read_file_content(test_file) or ""
248
-
249
250
  # Detect test style
250
251
  test_style = _detect_test_style(test_file)
251
252
  logger.debug(f"Detected test style for {test_file}: {test_style}")
@@ -255,10 +256,12 @@ def func(
255
256
 
256
257
  logger.info(f"Updating {test_file} for function '{function_name}'.")
257
258
  try:
259
+ existing_content = read_file_content(test_file)
258
260
  updated_test: str = asyncio.run(
259
- update_test_with_llm(source_code, test_code, str(file_path), [], other_tests_content, test_style)
261
+ update_test_with_llm(source_code, existing_content, str(file_path), [], other_tests_content, test_style)
260
262
  )
261
- write_file_content(test_file, updated_test)
263
+ new_content = insert_new_test(existing_content, updated_test)
264
+ write_file_content(test_file, new_content)
262
265
  logger.info(f"✅ Test file updated successfully: {test_file}")
263
266
  except Exception as exc: # pragma: no cover
264
267
  logger.error(f"Error updating {test_file}: {exc}")
@@ -54,6 +54,19 @@ def write_file_content(file_path: Path, content: str, mode: str = "w") -> None:
54
54
  f.write(content)
55
55
 
56
56
 
57
+ def insert_new_test(existing_content: str, new_test: str) -> str:
58
+ """
59
+ Inserts a new test into the existing content, before the `if __name__ == "__main__":` block if it exists.
60
+ """
61
+ main_guard = 'if __name__ == "__main__":'
62
+ if main_guard in existing_content:
63
+ parts = existing_content.split(main_guard)
64
+ # Ensure there's a newline before the new test and before the main guard
65
+ new_test = "\n\n" + new_test.strip()
66
+ return parts[0].rstrip() + new_test + "\n\n" + main_guard + parts[1]
67
+ return existing_content + "\n" + new_test
68
+
69
+
57
70
  def extract_function_source(file_path: str, function_name: str) -> str | None:
58
71
  """Extracts the source code of a specific function from a file."""
59
72
  try:
@@ -90,7 +90,8 @@ def test_main_auto_discovery(
90
90
  mock_find_test_file.assert_called_once_with(str(Path("src/main.py")), "tests")
91
91
  mock_read_file_content.assert_any_call(Path("tests/test_main.py"))
92
92
  mock_update_test_with_llm.assert_called_once()
93
- mock_write_file_content.assert_called_once_with(Path("tests/test_main.py"), "updated_test_code", mode="a")
93
+ assert mock_write_file_content.call_args[0][0] == Path("tests/test_main.py")
94
+ assert "updated_test_code" in mock_write_file_content.call_args[0][1]
94
95
 
95
96
 
96
97
  @patch("ai_unit_test.cli.write_file_content")
@@ -140,7 +141,8 @@ def test_main_explicit_args(
140
141
  mock_find_test_file.assert_called_once_with(str(Path("src/main.py")), "tests")
141
142
  mock_read_file_content.assert_any_call(Path("tests/test_main.py"))
142
143
  mock_update_test_with_llm.assert_called_once()
143
- mock_write_file_content.assert_called_once_with(Path("tests/test_main.py"), "updated_test_code", mode="a")
144
+ assert mock_write_file_content.call_args[0][0] == Path("tests/test_main.py")
145
+ assert "updated_test_code" in mock_write_file_content.call_args[0][1]
144
146
 
145
147
 
146
148
  @patch("ai_unit_test.cli.logger.error")
@@ -1,53 +0,0 @@
1
- name: Create Release
2
-
3
- on:
4
- push:
5
- branches:
6
- - main
7
-
8
- jobs:
9
- create_release:
10
- runs-on: ubuntu-latest
11
- permissions:
12
- contents: write # Para criar a tag e o release
13
-
14
- steps:
15
- - name: Checkout code
16
- uses: actions/checkout@v4
17
- with:
18
- fetch-depth: 0 # Necessário para obter todas as tags
19
-
20
- - name: Get latest version and calculate next
21
- id: get_next_version
22
- run: |
23
- LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0")
24
- echo "Latest tag: $LATEST_TAG"
25
-
26
- # Remove 'v' prefix and split into parts
27
- VERSION_PARTS=$(echo $LATEST_TAG | sed 's/^v//' | tr '.' '\n')
28
- MAJOR=$(echo $VERSION_PARTS | awk '{print $1}')
29
- MINOR=$(echo $VERSION_PARTS | awk '{print $2}')
30
- PATCH=$(echo $VERSION_PARTS | awk '{print $3}')
31
-
32
- # Increment patch version
33
- NEXT_PATCH=$((PATCH + 1))
34
- NEXT_VERSION="v${MAJOR}.${MINOR}.${NEXT_PATCH}"
35
- echo "Next version: $NEXT_VERSION"
36
-
37
- echo "NEXT_VERSION=$NEXT_VERSION" >> $GITHUB_OUTPUT
38
-
39
- - name: Create new tag
40
- run: |
41
- git config user.name "github-actions[bot]"
42
- git config user.email "github-actions[bot]@users.noreply.github.com"
43
- git tag -a ${{ steps.get_next_version.outputs.NEXT_VERSION }} -m "Release ${{ steps.get_next_version.outputs.NEXT_VERSION }}"
44
- git push origin ${{ steps.get_next_version.outputs.NEXT_VERSION }}
45
-
46
- - name: Create GitHub Release
47
- uses: softprops/action-gh-release@v1
48
- with:
49
- tag_name: ${{ steps.get_next_version.outputs.NEXT_VERSION }}
50
- name: Release ${{ steps.get_next_version.outputs.NEXT_VERSION }}
51
- body: Automated release created on push to main.
52
- draft: false
53
- prerelease: false
@@ -1,60 +0,0 @@
1
- name: Release
2
-
3
- on:
4
- release:
5
- types: [published]
6
-
7
- jobs:
8
- release:
9
- runs-on: ubuntu-latest
10
- permissions:
11
- contents: write # Para criar o release e a tag
12
- id-token: write # Para autenticação no PyPI
13
-
14
- steps:
15
- - name: Checkout code
16
- uses: actions/checkout@v4
17
- with:
18
- fetch-depth: 0 # Necessário para setuptools_scm
19
-
20
- - name: Set up Python
21
- uses: actions/setup-python@v5
22
- with:
23
- python-version: "3.x"
24
-
25
- - name: Install build dependencies
26
- run: |
27
- python -m pip install --upgrade pip
28
- pip install setuptools_scm build twine
29
-
30
- - name: Build package
31
- run: python -m build
32
-
33
- - name: Create GitHub Release
34
- uses: softprops/action-gh-release@v1
35
- with:
36
- tag_name: ${{ github.event.release.tag_name }}
37
- name: Release ${{ github.event.release.tag_name }}
38
- body: Automated release based on GitHub release event.
39
- draft: false
40
- prerelease: false
41
- files: dist/*
42
-
43
- - name: Publish to PyPI
44
- uses: pypa/gh-action-pypi-publish@release/v1
45
-
46
- - name: Set up Conda
47
- uses: conda-incubator/setup-miniconda@v3
48
- with:
49
- python-version: 3.x
50
- auto-update-conda: true
51
- auto-activate-base: false
52
-
53
- - name: Build and upload Conda package
54
- shell: bash -l {0}
55
- run: |
56
- conda install -c conda-forge conda-build anaconda-client -y
57
- export GIT_TAG=${{ github.event.release.tag_name }}
58
- conda build conda --output-folder conda-dist
59
- anaconda login --with-token ${{ secrets.CONDA_TOKEN }}
60
- anaconda upload conda-dist/**/*.tar.bz2 --label main --skip-existing
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes